Come implementare LessThan, ecc., Quando si costruiscono espressioni su stringhe

c# expression-trees linq

Domanda

Ho un pacchetto in cui sto costruendo alberi di espressioni, da utilizzare con EntityFramework, tramite PredicateBuilder:

public Expression<Func<T, bool>> constructaPredicate<T>(ExpressionType operation, string fieldName, Expression value)
{
    var type = typeof(T);
    var parameter = Expression.Parameter(type);
    var member = Expression.PropertyOrField(parameter, fieldName);
    Expression comparison = Expression.MakeBinary(operation, member, value);
    var expression = Expression.Lambda<Func<T, bool>>(comparison, parameter);
    return expression;
}

Funziona bene, tranne quando si confrontano stringhe con GreaterThan, ecc. In tal caso, ottengo un'eccezione:

The binary operator GreaterThan is not defined for the types 'System.String' and 'System.String'.

Che è abbastanza semplice Navigando in giro, ho trovato solo alcuni riferimenti a questo problema e nessuno nel contesto di ciò che sto facendo.

Il problema, ovviamente, è che non esiste un metodo String.GreaterThan. La solita risposta è usare String.CompareTo (), ma non ho capito come farlo funzionare.

Ho cercato di utilizzare il sovraccarico di Expression.MakeBinary che accetta un oggetto methodinfo, ma non l'ho capito.

Aiuto?

aggiunto

Quindi, ho provato a caso speciale String.GreaterThan, ecc. E sto ancora ricevendo lo stesso errore:

Expression comparison = null;

if (value.Type == typeof (string))
{
    if (operation == ExpressionType.GreaterThanOrEqual ||
        operation == ExpressionType.GreaterThan ||
        operation == ExpressionType.LessThanOrEqual ||
        operation == ExpressionType.LessThan)
    {
        var method = value.Type.GetMethod("CompareTo", new[] {typeof (string)});
        var zero = Expression.Constant(0);

        var result = Expression.Call(member, method, converted);

        comparison = Expression.MakeBinary(operation, result, zero);
    }
}

if (comparison == null)
    comparison = Expression.MakeBinary(operation, member, converted);

var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);

Ma sto ancora vedendo la stessa identica eccezione. Il che non ha senso per me, perché se sto facendo quello che penso di fare, l'unico GreaterThan nell'espressione sta confrontando Int32 con Int32.

Più aggiunto

Ho trovato molto strano che avrei visto lo stesso errore, dopo aver rimosso GreaterThan dal mio albero delle espressioni.

Ho eseguito questo codice come parte di un test unitario, con Entity Framework collegato a un database in memoria chiamato Effort . Così l'ho provato contro SqlServer.

Il mio codice originale, che non conteneva la stringa del caso speciale, ma utilizzava GreaterThan per tutto, lanciava l'eccezione "GreaterThan not defined", quando veniva eseguito contro SqlServer e quando veniva eseguito contro Effort.

Il mio codice modificato, quella stringa con involucro speciale, funzionava perfettamente con SqlServer, ma lanciava l'eccezione "GreaterThan not defined" quando si eseguiva contro lo sforzo.

Sembra che quando Effort vede un CompareTo () in un albero di espressioni, lo converte in un GreaterThan e ciò si traduce nella nostra eccezione familiare.

Ancora più aggiunto

Continuando ad esplorare il problema, ho determinato che c'è un bug in Effort, che può essere rivelato con un esempio molto più semplice:

var foos = myDbContext.Foos.Where(f => f.fooid.CompareTo("Z") > 0).ToList();

Funziona bene, quando myDbContext è connesso a un database SqlServer, genera la nostra eccezione preferita quando è connesso a un database di sforzo. Ho presentato una segnalazione di bug nel forum di discussione di Effort.

Per coloro che stanno leggendo questo, il mio secondo tentativo, nella mia prima sezione "Aggiunto", sopra, è la soluzione corretta. Funziona contro SqlServer, e che non contro lo sforzo è dovuto a un bug in sforzo.

appendice

La domanda è stata posta, a cosa si riferisce "convertito", in precedenza.

In verità, ricordo a malapena.

Quello che sta accadendo nel mio codice è che ho un albero di espressioni a cui sto applicando questi confronti. Sto usando Expression.Convert () per convertire questo nel tipo sottostante.

Non sono sicuro che il metodo completo abbia molto senso, in assenza del resto della classe, ma eccolo qui:

public Expression<Func<T, bool>> constructSinglePredicate<T>(object context)
{
    var type = typeof(T);
    var parameter = Expression.Parameter(type);
    var member = this.getMember<T>(type, parameter);
    var value = this.constructConstantExpression<T>(this.rightHandSide, context);
    ExpressionType operation;
    if (!operationMap.TryGetValue(this.selectionComparison, out operation))
        throw new ArgumentOutOfRangeException("selectionComparison", this.selectionComparison, "Invalid filter operation");

    try
    {
        var converted = (value.Type != member.Type)
            ? (Expression)Expression.Convert(value, member.Type)
            : (Expression)value;

        Expression comparison = null;

        if (value.Type == typeof(string))
        {
            if (operation == ExpressionType.GreaterThanOrEqual ||
                operation == ExpressionType.GreaterThan ||
                operation == ExpressionType.LessThanOrEqual ||
                operation == ExpressionType.LessThan)
            {
                MethodInfo method = value.Type.GetMethod("CompareTo", new[] { typeof(string) });
                var zero = Expression.Constant(0);
                var result = Expression.Call(member, method, converted);
                comparison = Expression.MakeBinary(operation, result, zero);
            }
        }

        if (comparison == null)
            comparison = Expression.MakeBinary(operation, member, converted);

        var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);

        return lambda;
    }
    catch (Exception)
    {
        throw new InvalidOperationException(
            String.Format("Cannot convert value \"{0}\" of type \"{1}\" to field \"{2}\" of type \"{3}\"", this.rightHandSide,
                value.Type, this.fieldName, member.Type));
    }
}

Risposta accettata

Questo funziona:

Expression comparison = null;

if (value.Type == typeof (string))
{
    if (operation == ExpressionType.GreaterThanOrEqual ||
        operation == ExpressionType.GreaterThan ||
        operation == ExpressionType.LessThanOrEqual ||
        operation == ExpressionType.LessThan)
    {
        var method = value.Type.GetMethod("CompareTo", new[] {typeof (string)});
        var zero = Expression.Constant(0);

        var result = Expression.Call(member, method, converted);

        comparison = Expression.MakeBinary(operation, result, zero);
    }
}

if (comparison == null)
    comparison = Expression.MakeBinary(operation, member, converted);

var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché