2013-03-23 12 views
16

Sto costruendo una query LINQ dinamicamente con questo codice. Sembra funzionare, ma quando ho più di una searchString nella mia ricerca, (in modo che quando vengono aggiunti molteplici espressioni, ottengo il seguente errore:espressioni LINQ. Variabile 'p' di tipo referenziato dall'ambito, ma non è definito

Variable 'p' of type referenced from scope, but it is not defined**

Credo di poter definire solo/uso p una volta. ma, se è così, ho bisogno di modificare il mio codice un po '. qualcuno mi può punto nella 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; 
    } 
+0

Non capisco. Sembra che il tuo 'CombinePredicates' si aspetta espressioni' n' e operatori 'n-1'. Tuttavia, nel posto in cui lo si invoca, si dispone di un array di operatori con lunghezza pari a '1'. Mi aspetterei un'eccezione di uscire dai limiti dell'array se ci sono più di predicati '2' da unire. –

+0

L'ho visto, ho tirato fuori alcune cose per rendere il mio esempio più compatto. Ma cambierò la mia domanda per rendere quella parte tecnicamente corretta. – Tys

+0

Ho corretto quella parte.Ma il problema rimane come era. – Tys

risposta

31

Semplificando, qui ci sono diverse linee che si sta tentando di fare (io uso stringa invece del 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 x e y - questo è esattamente il modo in cui appare per il compilatore, ovvero diversi parametri.

In altre parole, si stanno cercando di fare qualcosa di simile:

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

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

Per risolvere il problema, è necessario utilizzare esattamente lo stesso parametro (non solo il nome, ma anche il riferimento) per tutte le espressioni. Siamo in grado di risolvere questo codice semplificato in questo modo:

 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, fissare 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 bando l'output:

222 should not match 
111 222 should match 

Non è una cosa che si può 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 emesso:

111 222 should match 
+0

Grazie per l'elaborazione. Ci ho lavorato un po 'di più, e ora ho notato che quando il mio' cachedProductList 'proviene davvero da HttpContext.Current.Cache, di tanto in tanto ottengo variabile' x 'di tipo a cui fa riferimento l'ambito, ma non è definito. Quando non uso la memorizzazione nella cache, tutto funziona correttamente. Hai idea del perché questo sia? – Tys

+0

Hai un risultato reale nella cache (ad es. ... smthing.ToList()) o solo IEnumerable che viene eseguito ogni volta? Sembra che tu abbia una seconda opzione mentre hai bisogno del primo .. – Lanorkin

+0

No, ho archiviato l'elenco completo dei prodotti nella cache. E dopo voglio fare la mia ricerca su quella lista. – Tys

1

IMHO, non c'è bisogno di fare l'elenco:

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

Si può facilmente vivere con il seguente in 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 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.

Problemi correlati