2013-03-23 25 views
16

Tôi đang tạo một truy vấn LINQ động với mã này. Có vẻ như để làm việc, nhưng khi tôi có nhiều hơn một searchString trong tìm kiếm của tôi, (vì vậy khi nhiều biểu thức được thêm vào, tôi nhận được lỗi sau:Biểu thức LINQ. Biến 'p' của loại được tham chiếu từ phạm vi, nhưng nó không được xác định

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

Tôi đoán tôi chỉ có thể xác định/sử dụng p một lần. nhưng, nếu như vậy, tôi cần phải thay đổi mã của tôi một chút. Bất cứ ai có chỉ cho tôi đi đúng hướng ở đây?

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

Tôi không hiểu lắm. Dường như 'CombinePredicates' của bạn mong đợi các biểu thức' n' và 'n-1'. Tuy nhiên, tại nơi bạn gọi nó, bạn có một loạt các toán tử có độ dài '1'. Tôi sẽ mong đợi một ngoại lệ đi ra khỏi giới hạn của mảng nếu có nhiều hơn '2' predicates để tham gia. –

+0

Tôi đã thấy rằng, tôi đã lấy ra một số công cụ để làm cho ví dụ của tôi nhỏ gọn hơn. Nhưng tôi sẽ thay đổi câu hỏi của tôi để làm cho phần đó về mặt kỹ thuật chính xác. – Tys

+0

Tôi đã sửa phần đó.Nhưng vẫn còn vấn đề vẫn còn như nó được. – Tys

Trả lời

31

Đơn giản hóa, sau đây là một vài dòng mà bạn đang cố gắng làm (tôi sử dụng chuỗi thay vì sản phẩm vv , nhưng ý tưởng là như nhau):

 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 

Xin lưu ý làm thế nào tôi mở rộng foreach của bạn thành hai biểu thức với x và y - đây là chính xác như thế nào nó trông giống như đối với trình biên dịch, đó là khác nhau tham số.

Nói cách khác, bạn đang cố gắng để làm một cái gì đó như thế này:

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

và biên dịch tự hỏi những gì mà biến 'y' là ??

Để khắc phục điều đó, chúng ta cần phải sử dụng chính xác các thông số tương tự (không chỉ là tên, mà còn tham khảo) cho tất cả các biểu thức. Chúng ta có thể sửa mã đơn giản hóa này như thế này:

 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 

Vì vậy, sửa chữa bạn mã gốc sẽ như thế nào:

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

Nhưng hãy chú ý đầu ra:

222 should not match 
111 222 should match 

Không phải cái gì bạn có thể mong đợi .. Đây là kết quả của việc sử dụng searchString trong foreach, cần được viết lại theo cách sau:

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

Và đây là kết quả:

111 222 should match 
+0

Cảm ơn bạn đã xây dựng. Tôi đã làm việc này nhiều hơn một chút, và bây giờ tôi đã nhận thấy rằng khi 'cachedProductList' của tôi thực sự xuất phát từ HttpContext.Current.Cache, theo thời gian một lần nữa tôi nhận được biến 'x' của kiểu được tham chiếu từ phạm vi, nhưng nó không được xác định. Khi tôi không sử dụng bộ nhớ đệm tất cả mọi thứ hoạt động tốt. Bạn có bất kỳ ý tưởng tại sao điều này? – Tys

+0

Bạn có kết quả thực tế trong bộ nhớ cache (ví dụ: ... smthing.ToList()) hoặc chỉ IEnumerable thực thi mọi lúc? Có vẻ như bạn có tùy chọn thứ hai trong khi bạn cần tùy chọn thứ nhất .. – Lanorkin

+0

Không, tôi lưu trữ danh sách đầy đủ các sản phẩm trong bộ nhớ cache. Và sau đó tôi muốn thực hiện tìm kiếm của tôi trên danh sách đó. – Tys

1

IMHO, không cần phải làm cho danh sách:

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

Bạn dễ dàng có thể sống với những điều sau đây trong lớp học Lượt truy cập:

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

Các mã không phải là tối ưu, tôi biết, tôi là một người mới bắt đầu và rất nhiều thứ được thực hiện ... Nhưng công cụ này không hoạt động. Ý tưởng là chỉ có ParameterExpression trên mỗi lớp Visitor, sau đó để xây dựng các biểu thức bằng cách sử dụng tham số này. Sau đó, chỉ cần ghép nối tất cả các biểu thức trên một mệnh đề LambdaExpression và biên dịch thành đại biểu, khi cần thiết.

Các vấn đề liên quan