linq aux entités dynamiques où construire à partir de lambdas

c# entity-framework expression-trees lambda linq

Question

j'ai un ensemble de lambdas comme celui-ci

t => t.FirstName
t => t.LastName
t => t.Profession

Je voudrais trouver un moyen de construire une expression qui peut être utilisée dans une instruction Where dans Linq to Entities où ces lambdas sont comparés à une valeur à l'aide de string.contains

// a filter is definded by a lambda and the string to compare it with   
var filters = new Dictionary<Expression<Func<Person, string>>, string>();
filters.Add(t => t.FirstName, "Miller");
filters.Add(t => t.Profession, "Engineer");
var filterConstraints = BuildFilterExpression(t => t, filters);
Entities.Persons.Where(filterConstraints).ToList();

public static Expression<Func<TElement, bool>> BuildFilterExpression<TElement>(Dictionary<Expression<Func<TElement, string>>, string> constraints)
{
  List<Expression> expressions = new List<Expression>();

  var stringType = typeof(string);
  var containsMethod = stringType.GetMethod("Contains", new Type[] { stringType });

  foreach (var constraint in constraints)
  {
    var equalsExpression = (Expression)Expression.Call(constraint.Key.Body, containsMethod, Expression.Constant(constraint.Value, stringType));
    expressions.Add(equalsExpression);
  }

  var body = expressions.Aggregate((accumulate, equal) => Expression.And(accumulate, equal));

  ParameterExpression p = constraints.First().Key.Parameters.First();
  return Expression.Lambda<Func<TElement, bool>>(body, p);
}

Je suppose que je fais quelque chose de terriblement faux dans la construction de l'arbre d'expression car j'obtiens l'exception suivante: exception d'opération non valide : le paramètre 't' n'était pas lié à l'expression de requête LINQ to Entities spécifiée.

Est-ce que quelqu'un sait comment résoudre ce problème?

Réponse acceptée

Vous êtes vraiment très proche. Le problème est que les objets de paramètre qui ont le même nom et le même type, ne sont pas techniquement "égaux".

var b = Expression.Parameter(typeof(string), "p") == 
    Expression.Parameter(typeof(string), "p");
//b is false

Ainsi, le paramètre du lambda que vous créez est le paramètre de la première expression que vous prenez en entrée. Les paramètres utilisés dans le corps de toutes les autres expressions sont des paramètres différents , et ils ne sont pas donnés en tant que paramètres au lambda, donc l'erreur est due à cela.

La solution est en fait assez simple. Il vous suffit de remplacer toutes les occurrences de tous les autres paramètres par le paramètre réel que vous souhaitez utiliser.

Voici une méthode d'assistance (utilisant une classe d'assistance) qui prend toutes les occurrences d'une expression dans une expression et la remplace par une autre:

public class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

Nous appelons maintenant cela une fois sur chaque corps, en remplaçant dans un paramètre commun:

public static Expression<Func<TElement, bool>> BuildFilterExpression<TElement>(
    Dictionary<Expression<Func<TElement, string>>, string> constraints)
{
    List<Expression> expressions = new List<Expression>();

    var stringType = typeof(string);
    var containsMethod = stringType.GetMethod("Contains", new Type[] { stringType });

    var parameter = Expression.Parameter(typeof(TElement));

    foreach (var constraint in constraints)
    {
        var equalsExpression = (Expression)Expression.Call(
            constraint.Key.Body.Replace(constraint.Key.Parameters[0], parameter),
            containsMethod, Expression.Constant(constraint.Value, stringType));
        expressions.Add(equalsExpression);
    }

    var body = expressions.Aggregate((accumulate, equal) =>
        Expression.And(accumulate, equal));

    return Expression.Lambda<Func<TElement, bool>>(body, parameter);
}

Réponse populaire

Fermer. Malheureusement, par exemple, si vous regardez dans chacun de vos lambdas de propriété,

t => t.FirstName 
t => t.LastName

Vous constaterez qu'ils sont chacun Expression.Property . Cependant, chacun d'eux a un Expression.Parameter différent. Vous souhaitez utiliser un ExpressionVisitor pour remplacer PropertyExpression.Parameter par le même INSTANCE que Expression.Parameter AND et l'utiliser avec Expression.Lambda .

Exception Invalid operation exception - Le paramètre 't' n'était pas lié dans l'expression de requête LINQ to Entities spécifiée. signifie que vous avez dans votre corps lambda des expressions ParameterExpression qui ne figurent pas dans le tableau de paramètres de lambda.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow