LINQ-Ausdrücke. Die Variable 'p' des Typs, der vom Gültigkeitsbereich referenziert wird, ist jedoch nicht definiert

c# expression-trees linq

Frage

Ich erstelle eine LINQ-Abfrage dynamisch mit diesem Code. Es scheint zu funktionieren, aber wenn ich mehr als einen Suchstring in meiner Suche habe, (wenn mehrere Ausdrücke hinzugefügt werden, erhalte ich den folgenden Fehler:

Die Variable 'p' des Typs, der vom Geltungsbereich referenziert wird, ist jedoch nicht definiert. **

Ich denke, ich kann p nur einmal definieren / verwenden. Aber wenn das so ist, muss ich meinen Code etwas ändern. Kann mir hier jemand in die richtige Richtung zeigen?

    if (searchStrings != null)
    {
        foreach (string searchString in searchStrings)
        {
            Expression<Func<Product, bool>> containsExpression = p => p.Name.Contains(searchString);
            filterExpressions.Add(containsExpression);
        }
    }

    Func<Expression, Expression, BinaryExpression>[] operators = new Func<Expression, Expression, BinaryExpression>[] { Expression.AndAlso };
    Expression<Func<Product, bool>> filters = this.CombinePredicates<Product>(filterExpressions, operators);

    IQueryable<Product> query = cachedProductList.AsQueryable().Where(filters);

    query.Take(itemLimit).ToList();  << **error when the query executes**


    public Expression<Func<T, bool>> CombinePredicates<T>(IList<Expression<Func<T, bool>>> predicateExpressions, Func<Expression, Expression, BinaryExpression> logicalFunction)
    {
        Expression<Func<T, bool>> filter = null;

        if (predicateExpressions.Count > 0)
        {
            Expression<Func<T, bool>> firstPredicate = predicateExpressions[0];
            Expression body = firstPredicate.Body;
            for (int i = 1; i < predicateExpressions.Count; i++)
            {
                body = logicalFunction(body, predicateExpressions[i].Body);
            }
            filter = Expression.Lambda<Func<T, bool>>(body, firstPredicate.Parameters);
        }

        return filter;
    }

Akzeptierte Antwort

Vereinfachend, hier sind einige Zeilen, die Sie versuchen zu tun (Ich verwende String statt Produkt etc, aber die Idee ist das gleiche):

        Expression<Func<string, bool>> c1 = x => x.Contains("111");
        Expression<Func<string, bool>> c2 = y => y.Contains("222");
        var sum = Expression.AndAlso(c1.Body, c2.Body);
        var sumExpr = Expression.Lambda(sum, c1.Parameters);
        sumExpr.Compile(); // exception here

Bitte beachten Sie, wie ich Ihre foreach in zwei Ausdrücke mit x und y erweitert habe - genau so sieht es für Compiler aus, das sind verschiedene Parameter.

Mit anderen Worten, Sie versuchen, so etwas zu tun:

x => x.Contains("...") && y.Contains("...");

und Compiler fragt sich, was ist das 'y' Variable?

Um es zu beheben, müssen wir für alle Ausdrücke genau denselben Parameter verwenden (nicht nur Namen, sondern auch Referenz). Wir können diesen vereinfachten Code wie folgt reparieren:

        Expression<Func<string, bool>> c1 = x => x.Contains("111");
        Expression<Func<string, bool>> c2 = y => y.Contains("222");
        var sum = Expression.AndAlso(c1.Body, Expression.Invoke(c2, c1.Parameters[0])); // here is the magic
        var sumExpr = Expression.Lambda(sum, c1.Parameters);
        sumExpr.Compile(); //ok

Der ursprüngliche Code wäre also wie folgt:

internal static class Program
{
    public class Product
    {
        public string Name;
    }

    private static void Main(string[] args)
    {
        var searchStrings = new[] { "111", "222" };
        var cachedProductList = new List<Product>
        {
            new Product{Name = "111 should not match"},
            new Product{Name = "222 should not match"},
            new Product{Name = "111 222 should match"},
        };

        var filterExpressions = new List<Expression<Func<Product, bool>>>();
        foreach (string searchString in searchStrings)
        {
            Expression<Func<Product, bool>> containsExpression = x => x.Name.Contains(searchString); // NOT GOOD
            filterExpressions.Add(containsExpression);
        }

        var filters = CombinePredicates<Product>(filterExpressions, Expression.AndAlso);

        var query = cachedProductList.AsQueryable().Where(filters);

        var list = query.Take(10).ToList();
        foreach (var product in list)
        {
            Console.WriteLine(product.Name);
        }
    }

    public static Expression<Func<T, bool>> CombinePredicates<T>(IList<Expression<Func<T, bool>>> predicateExpressions, Func<Expression, Expression, BinaryExpression> logicalFunction)
    {
        Expression<Func<T, bool>> filter = null;

        if (predicateExpressions.Count > 0)
        {
            var firstPredicate = predicateExpressions[0];
            Expression body = firstPredicate.Body;
            for (int i = 1; i < predicateExpressions.Count; i++)
            {
                body = logicalFunction(body, Expression.Invoke(predicateExpressions[i], firstPredicate.Parameters));
            }
            filter = Expression.Lambda<Func<T, bool>>(body, firstPredicate.Parameters);
        }

        return filter;
    }
}

Aber beachte die Ausgabe:

222 should not match
111 222 should match

Nicht etwas, was Sie erwarten können. Dies ist das Ergebnis der Verwendung von searchString in foreach, die folgendermaßen umgeschrieben werden sollte:

        ...
        foreach (string searchString in searchStrings)
        {
            var name = searchString;
            Expression<Func<Product, bool>> containsExpression = x => x.Name.Contains(name);
            filterExpressions.Add(containsExpression);
        }
        ...

Und hier ist Ausgabe:

111 222 should match

Beliebte Antwort

IMHO, keine Notwendigkeit, die Liste zu machen:

var filterExpressions = new List<Expression<Func<Product, bool>>>()

In der Besucherklasse können Sie problemlos mit folgenden Personen leben:

public class FilterConverter : IFilterConverterVisitor<Filter> {

    private LambdaExpression ConditionClausePredicate { get; set; }
    private ParameterExpression Parameter { get; set; }

    public void Visit(Filter filter) {

        if (filter == null) {
            return;
        }

        if (this.Parameter == null) {
            this.Parameter = Expression.Parameter(filter.BaseType, "x");
        }

        ConditionClausePredicate = And(filter);
    }

    public Delegate GetConditionClause() {

        if (ConditionClausePredicate != null) {

            return ConditionClausePredicate.Compile();
        }

        return null;
    }

    private LambdaExpression And(Filter filter) {

        if (filter.BaseType == null || string.IsNullOrWhiteSpace(filter.FlattenPropertyName)) {

            //Something is wrong, passing by current filter
            return ConditionClausePredicate;
        }

        var conditionType = filter.GetCondition();
        var propertyExpression = filter.BaseType.GetFlattenPropertyExpression(filter.FlattenPropertyName, this.Parameter);

        switch (conditionType) {

            case FilterCondition.Equal: {

                var matchValue = TypeDescriptor.GetConverter(propertyExpression.ReturnType).ConvertFromString(filter.Match);
                var propertyValue = Expression.Constant(matchValue, propertyExpression.ReturnType);
                var equalExpression = Expression.Equal(propertyExpression.Body, propertyValue);
                if (ConditionClausePredicate == null) {
                    ConditionClausePredicate = Expression.Lambda(equalExpression, this.Parameter);
                } else {
                    ConditionClausePredicate = Expression.Lambda(Expression.And(ConditionClausePredicate.Body, equalExpression), this.Parameter);
                }
                break;
            }
        // and so on...
    }
}

Der Code ist nicht optimal, ich weiß, ich bin ein Anfänger und eine Menge von allem, was zu implementieren ist ... Aber dieses Zeug funktioniert. Die Idee ist, die einzige ParameterExpression pro Visitor-Klasse zu haben, um Ausdrücke mit diesem Parameter zu konstruieren. Danach verketten Sie einfach alle Ausdrücke mit einer LambdaExpression-Klausel und kompilieren Sie, um sie bei Bedarf zu delegieren.



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