linq alle entità dinamiche dove costruire da lambda

c# entity-framework expression-trees lambda linq

Domanda

ho un set di lambda come questo

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

Vorrei trovare un modo per creare un'espressione che può essere utilizzata in un'istruzione Where in Linq alle entità in cui questi lambda vengono confrontati con un valore utilizzando 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);
}

Suppongo che sto facendo qualcosa di terribilmente sbagliato nella costruzione dell'albero delle espressioni perché ottengo la seguente eccezione: Eccezione operazione non valida - Il parametro 't' non era associato nell'espressione di query LINQ to Entities specificata.

Qualcuno sa come risolvere questo problema?

Risposta accettata

Sei davvero molto vicino. Il problema è che gli oggetti parametro che hanno lo stesso nome e tipo, non sono tecnicamente "uguali".

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

Quindi il parametro della lambda che crei è il parametro della prima espressione che prendi come input. I parametri utilizzati nel corpo di tutte le altre espressioni sono parametri diversi e non vengono forniti come parametri per lambda, quindi l'errore è dovuto a ciò.

La soluzione è in realtà abbastanza semplice. Hai solo bisogno di sostituire tutte le istanze di tutti gli altri parametri con il parametro attuale che si desidera utilizzare.

Ecco un metodo di supporto (che usa una classe helper) che prende tutte le istanze di un'espressione in alcune espressioni e la sostituisce con un'altra:

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

Ora lo chiamiamo solo una volta su ogni corpo, sostituendolo con un parametro comune:

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

Risposta popolare

Vicino. Sfortunatamente se guardi dentro ognuno dei tuoi agnelli di proprietà, ad esempio ..

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

Troverete che sono ciascuno di Expression.Property . Tuttavia ognuno di essi ha un diverso Expression.Parameter . Si desidera utilizzare un ExpressionVisitor per sostituire PropertyExpression.Parameter con lo stesso INSTANCE di Expression.Parameter e utilizzarlo con Expression.Lambda .

Eccezione dell'operazione Eccezione non valida - Il parametro 't' non è stato associato nell'espressione di query LINQ to Entities specificata. significa che hai ParameterExpression s nel tuo corpo di lambda che non si trova nell'array dei parametri di lambda.



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é