2013-03-23 16 views
16

私はこのコードでLINQクエリを動的に構築しています。LINQ式。スコープから参照される型の変数 'p'が定義されていません

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

私は一度だけ/使用pを定義することができますね。私は私の検索で複数のsearchStringのを持っている場合 複数の表現が追加されたときに、私は次のエラーを取得する(、動作しているようですが、。しかし、もしそうなら、私はあなたが私が代わりに製品などの文字列を使用(しようとしているいくつかの行をここで、簡素化

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

私はそれを得ていません。あなたの 'CombinePredicates'は' n'式と 'n-1'演算子を期待しているようです。しかし、あなたが呼び出す場所には、長さが '1'の演算子の配列があります。結合する述語が2つ以上ある場合、配列の範囲外に出ることを除いて、私は例外を予期します。 –

+0

私はそれを見ました、私は私の例をよりコンパクトにするためにいくつかのものを取り出しました。しかし、私はその部分を技術的に正しいものにするために私の質問を変えます。 – Tys

+0

私はその部分を修正しました。しかし、それでも問題はそのままです。 – Tys

答えて

31

?誰もがここで正しい方向に私を指すことができます。私のコードビットを変更する必要があります考えは同じです):

 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 

私はforeachをxとyの2つの式にどのように拡張したかに注意してください。これはコンパイラのように、の異なるパラメータとまったく同じように見えます。言い換えれば

、あなたはこのような何かやろうとしている:「Y」変数ということであるか疑問に思う

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

とコンパイラを??

これを修正するには、すべての式に対して同じパラメータ(名前だけでなく参照値)を使用する必要があります。

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

しかし、出力を確認します:あなたは、元のコードは次のようになり、固定、そう

 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 

:私たちは、このように、この単純化されたコードを修正することができます

222 should not match 
111 222 should match 

ないあなたが期待する何かこれはforeach内のsearchStringを使用した結果です。これは次のように書き直す必要があります。

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

そして、ここで出力されます:

111 222 should match 
+0

ご協力ありがとうございます。私はもう少しこれに取り組んで、今私は私の 'cachedProductList'が実際にはHttpContext.Current.Cacheから来ていることに気づきました、時々、私はスコープから参照される型のVariable 'x'を取得しますが、定義された。私はキャッシュを使用していないときはすべて正常に動作します。あなたはこれがなぜなのか考えていますか? – Tys

+0

キャッシュに実際の結果がありますか(つまり... smthing.ToList())、毎回実行されるIEnumerableだけですか?最初のものが必要な間に2番目のオプションがあるように見えます。 – Lanorkin

+0

いいえ、私はキャッシュに製品の完全なリストを保存します。その後、私はそのリストで自分の検索を実行したい。 – Tys

1

IMHO、リストを作成する必要はありません:

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

あなたは簡単にビジタークラスで、次のと一緒に暮らすことがあります。

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

コードは最適ではありません、私は、私は初心者であり、実装すべきことはたくさんあります...しかし、このことはうまくいきます。この考え方は、Visitorクラスごとに唯一のParameterExpressionを持ち、このパラメータを使用して式を構築することです。その後、1つのLambdaExpression節ごとにすべての式を連結し、必要に応じてコンパイルして委譲します。

関連する問題