Comment implémenter LessThan, etc., lors de la construction d'expressions sur des chaînes

c# expression-trees linq

Question

J'ai un paquet dans lequel je construis des arbres d'expression, à utiliser avec EntityFramework, via 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;
}

Cela fonctionne très bien, sauf pour comparer des chaînes avec GreaterThan, etc. Dans ce cas, je reçois une exception:

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

Ce qui est assez simple. En parcourant les pages, je n'ai trouvé que quelques références à ce problème, et aucune dans le contexte de ce que je fais.

Le problème, bien sûr, est qu’il n’existe pas de méthode String.GreaterThan. La réponse habituelle consiste à utiliser String.CompareTo (), mais je n'ai pas encore trouvé comment faire fonctionner cela.

J'ai essayé d'utiliser la surcharge d'Expression.MakeBinary qui prend un objet methodinfo, mais je ne l'ai pas encore compris.

Aidez-moi?

Ajoutée

Donc, j'ai essayé de faire un cas spécial String.GreaterThan, etc., et j'obtiens toujours la même erreur:

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);

Mais je vois toujours exactement la même exception. Ce qui n'a aucun sens pour moi, car si je fais ce que je pense, le seul GreaterThan dans l'expression compare Int32 à Int32.

Plus ajouté

J'ai trouvé très étrange de voir la même erreur après avoir supprimé GreaterThan de mon arbre d'expression.

J'exécutais ce code dans le cadre d'un test unitaire, Entity Framework étant connecté à une base de données en mémoire appelée Effort . Alors je l'ai essayé contre SqlServer.

Mon code d'origine, qui ne comportait pas de chaîne de casse particulière, mais utilisait GreaterThan pour tout, a généré l'exception "GreaterThan non définie", lorsqu'il était exécuté sur SqlServer et contre Effort.

Mon code modifié, cette chaîne spéciale, fonctionnait parfaitement contre SqlServer, mais lançait l'exception "GreaterThan not defin" lors de son exécution avec Effort.

Il semble que lorsque Effort voit un CompareTo () dans un arbre d’expression, il le convertisse en GreaterThan, ce qui entraîne notre exception familière.

Encore plus ajouté

En continuant d'explorer la question, j'ai déterminé qu'il y avait un bogue dans Effort, qui peut être révélé avec un exemple bien plus simple:

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

Cela fonctionne bien, lorsque myDbContext est connecté à une base de données SqlServer, il lève notre exception favorite lorsqu'il est connecté à une base de données Effort. J'ai déposé un rapport de bogue sur le forum de discussion Effort.

Pour ceux qui lisent ceci, ma deuxième tentative, dans ma première section "Ajoutée", ci-dessus, est la solution correcte. Cela fonctionne contre SqlServer, et cela ne fonctionne pas contre Effort est dû à un bogue dans Effort.

Addenda

La question a été posée: à quoi le terme "converti" fait-il référence dans ce qui précède?

En vérité, je m'en souviens à peine.

Ce qui se passe dans mon code, c’est que j’applique ces comparaisons à un arbre d’expression. J'utilise Expression.Convert () pour convertir cela en type sous-jacent.

Je ne suis pas sûr que la méthode complète ait beaucoup de sens, en l'absence du reste de la classe, mais la voici:

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));
    }
}

Réponse acceptée

Cela marche:

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);


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi