linq zu Entitäten dynamisch, wo aus lambdas aufbauen

c# entity-framework expression-trees lambda linq

Frage

Ich habe eine Reihe von Lambdas wie folgt

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

Ich möchte eine Möglichkeit finden, einen Ausdruck zu erstellen, der in einer Where- Anweisung in Linq to Entities verwendet werden kann, wobei diese Lambda- Werte mit einem Wert verglichen werden, der string.contains verwendet

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

Ich denke, ich mache etwas schrecklich falsch beim Erstellen der Ausdrucksstruktur, weil ich die folgende Ausnahme bekomme: Ungültige Operation Ausnahme - Der Parameter 't' wurde nicht in der angegebenen LINQ to Entities Abfrage Ausdruck gebunden.

Weiß jemand, wie man dieses Problem löst?

Akzeptierte Antwort

Du bist wirklich sehr nah dran. Das Problem ist, dass Parameterobjekte, die den gleichen Namen und Typ haben, technisch nicht "gleich" sind.

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

Der Parameter des von Ihnen erstellten Lambda ist also der Parameter des ersten Ausdrucks, den Sie als Eingabe verwenden. Die Parameter, die im Rumpf aller anderen Ausdrücke verwendet werden, sind andere Parameter , und sie werden nicht als Parameter für das Lambda angegeben, daher liegt der Fehler daran.

Die Lösung ist eigentlich ziemlich einfach. Sie müssen nur alle Instanzen aller anderen Parameter durch den tatsächlichen Parameter ersetzen, den Sie verwenden möchten.

Hier ist eine Hilfsmethode (mit einer Hilfsklasse), die alle Instanzen eines Ausdrucks in einem Ausdruck übernimmt und durch einen anderen ersetzt:

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

Jetzt nennen wir das einmal an jedem Körper und ersetzen es in einem gemeinsamen Parameter:

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

Beliebte Antwort

Schließen. Leider, wenn Sie zum Beispiel in jedes Ihrer Immobilien-Lambdas schauen.

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

Sie werden feststellen, dass es sich jeweils um Expression.Property . Allerdings hat jeder von ihnen einen anderen Expression.Parameter . Sie mögen ein verwenden ExpressionVisitor die ersetzen PropertyExpression.Parameter mit der gleichen Instanz von Expression.Parameter und das bei Verwendung Expression.Lambda .

Die Exception Ungültige Operationsausnahme - Der Parameter 't' wurde im angegebenen LINQ to Entities Query-Ausdruck nicht gebunden. bedeutet, dass Sie ParameterExpression s in Ihrem Lambda-Körper haben, die nicht im Lambda-Parameter-Array sind.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum