Espressioni LINQ. La variabile 'p' di tipo a cui fa riferimento l'ambito, ma non è definita

c# expression-trees linq

Domanda

Sto creando una query LINQ in modo dinamico con questo codice. Sembra funzionare, ma quando ho più di un searchString nella mia ricerca, (così quando vengono aggiunte più espressioni, ottengo il seguente errore:

Variabile 'p' di tipo a cui fa riferimento l'ambito, ma non è definito **

Immagino di poter solo definire / usare p una volta. Ma, se è così, ho bisogno di modificare il mio codice un po '. Qualcuno può indicarmi la giusta direzione qui?

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

Risposta accettata

Semplificando, qui ci sono diverse righe che stai cercando di fare (io uso stringa invece Prodotto ecc, ma l'idea è la stessa):

        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

Si noti come ho ampliato il foreach in due espressioni con xey - questo è esattamente il modo in cui appare per il compilatore, che sono parametri diversi .

In altre parole, stai provando a fare qualcosa del genere:

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

e compilatore chiedendo che cosa è quella variabile 'y' ??

Per risolverlo, dobbiamo usare esattamente lo stesso parametro (non solo il nome, ma anche il riferimento) per tutte le espressioni. Possiamo risolvere questo codice semplificato come questo:

        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

Quindi, correggendo il tuo codice originale sarebbe come:

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

Ma nota l'output:

222 should not match
111 222 should match

Non qualcosa che ci si potrebbe aspettare .. Questo è il risultato dell'uso di searchString in foreach, che dovrebbe essere riscritto nel modo seguente:

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

E qui viene prodotto:

111 222 should match

Risposta popolare

IMHO, non c'è bisogno di fare la lista:

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

Puoi facilmente vivere con il seguente nella classe Visitor:

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...
    }
}

Il codice non è ottimale, lo so, sono un principiante e molto di tutto da implementare ... Ma questa roba funziona. L'idea è di avere l'unica classe ParameterExpression per Visitor, quindi di costruire espressioni usando questo parametro. Dopo, concatenare tutte le espressioni per una clausola LambdaExpression e compilare per delegare, quando necessario.



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é