LINQ表达式。 从范围引用的变量’p’,但未定义

我正在使用此代码动态构建LINQ查询。 它似乎工作,但当我在我的搜索中有多个searchString时(所以当添加多个表达式时,我得到以下错误:

从范围引用的变量’p’,但未定义**

我想我只能定义/使用p一次。 但是,如果是这样,我需要稍微改变我的代码。 任何人都能指出我在正确的方向吗?

if (searchStrings != null) { foreach (string searchString in searchStrings) { Expression<Func> containsExpression = p => p.Name.Contains(searchString); filterExpressions.Add(containsExpression); } } Func[] operators = new Func[] { Expression.AndAlso }; Expression<Func> filters = this.CombinePredicates(filterExpressions, operators); IQueryable query = cachedProductList.AsQueryable().Where(filters); query.Take(itemLimit).ToList(); << **error when the query executes** public Expression<Func> CombinePredicates(IList<Expression<Func>> predicateExpressions, Func logicalFunction) { Expression<Func> filter = null; if (predicateExpressions.Count > 0) { Expression<Func> 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>(body, firstPredicate.Parameters); } return filter; } 

简化,这里有几行你要尝试(我使用字符串代替产品等,但想法是一样的):

  Expression> c1 = x => x.Contains("111"); Expression> 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的两个表达式 – 这正是编译器的样子,它们是不同的参数。

换句话说,你正试图做这样的事情:

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

和编译器想知道’y’变量是什么?

要修复它,我们需要为所有表达式使用完全相同的参数(不仅仅是名称,还要引用)。 我们可以像这样修复这个简化的代码:

  Expression> c1 = x => x.Contains("111"); Expression> 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 

因此,修复原始代码将是:

 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 { 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>>(); foreach (string searchString in searchStrings) { Expression> containsExpression = x => x.Name.Contains(searchString); // NOT GOOD filterExpressions.Add(containsExpression); } var filters = CombinePredicates(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> CombinePredicates(IList>> predicateExpressions, Func logicalFunction) { Expression> 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>(body, firstPredicate.Parameters); } return filter; } } 

但请注意输出:

 222 should not match 111 222 should match 

不是你可能期望的……这是在foreach中使用searchString的结果,应该用以下方式重写:

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

这是输出:

 111 222 should match 

恕我直言,无需列出清单:

 var filterExpressions = new List>>() 

您可以轻松使用Visitor类中的以下内容:

 public class FilterConverter : IFilterConverterVisitor { 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,然后使用此参数构造表达式。 之后,只需连接每个LambdaExpression子句的所有表达式,并在需要时编译为委托。