Ответ 1
Упрощение, вот несколько строк, которые вы пытаетесь сделать (я использую строку вместо Product и т.д., Но идея такая же):
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 - это точно так, как это выглядит для компилятора, это разные параметры.
Другими словами, вы пытаетесь сделать что-то вроде этого:
x => x.Contains("...") && y.Contains("...");
и компилятор задается вопросом, что это за переменная '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
Итак, исправление исходного кода было бы следующим:
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;
}
}
Но заметьте вывод:
222 should not match
111 222 should match
Не то, что вы можете ожидать.. Это результат использования searchString в foreach, который следует переписать следующим образом:
...
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