2009-12-17 2 views
18

У меня есть форма с несколькими полями на ней (название компании, почтовый индекс и т. Д.), Которая позволяет пользователю искать компании в базе данных. Если пользователь вводит значения в более чем одном поле, мне нужно выполнить поиск по всем этим полям. Я использую LINQ для запроса базы данных.Как объединить выражения LINQ в один?

До сих пор мне удалось написать функцию, которая будет смотреть на их вход и превратить ее в список выражений. Теперь я хочу превратить этот список в одно выражение, которое затем я могу выполнить через поставщика LINQ.

Моя первая попытка была следующей

private Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions) 
    { 
     if (expressions.Count == 0) 
     { 
      return null; 
     } 
     if (expressions.Count == 1) 
     { 
      return expressions[0]; 
     } 
     Expression<Func<Company, bool>> combined = expressions[0]; 
     expressions.Skip(1).ToList().ForEach(expr => combined = Expression.And(combined, expr)); 
     return combined; 
    } 

Однако это не удается с сообщением исключения по линиям «Бинарный оператор А не определена для ...». У кого-нибудь есть идеи, что мне нужно сделать, чтобы объединить эти выражения?

EDIT: Исправлена ​​строка, в которой я забыл присвоить результат и преобразовать выражения в переменную. Спасибо, что указали, что люди.

ответ

9

EDIT: ответ Джейсона теперь более полный, чем мой, с точки зрения дерева выражений, поэтому я удалил этот бит. Однако я хотел бы оставить это:

Я предполагаю, что вы используете их для предложения Where ... почему бы просто не позвонить Где с каждым выражением в свою очередь? Это должно иметь тот же эффект:

var query = ...; 
foreach (var condition in conditions) 
{ 
    query = query.Where(condition); 
} 
+1

@Jon Skeet: 'комбинированный' будет набираться как' Expression'; вам нужно выполнить некоторую работу, чтобы вернуть его как выражение > '. – jason

+0

Я согласен, что ваш первый код легче понять, поэтому я сделаю правильный ответ. Однако я действительно собираюсь использовать второй фрагмент, потому что это именно то, что мне нужно - я делал вещи слишком сложными, благодаря Джону. – gilles27

+1

По иронии судьбы, я редактировал, когда оба этих комментария были написаны, но поскольку это был второй фрагмент, который использовался, я думаю, что оставлю его как есть :) –

22

Вы можете использовать Enumerable.Aggregate в сочетании с Expression.AndAlso. Вот общая версия:

Expression<Func<T, bool>> AndAll<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions) { 

    if(expressions == null) { 
     throw new ArgumentNullException("expressions"); 
    } 
    if(expressions.Count() == 0) { 
     return t => true; 
    } 
    Type delegateType = typeof(Func<,>) 
          .GetGenericTypeDefinition() 
          .MakeGenericType(new[] { 
           typeof(T), 
           typeof(bool) 
          } 
         ); 
    var combined = expressions 
         .Cast<Expression>() 
         .Aggregate((e1, e2) => Expression.AndAlso(e1, e2)); 
    return (Expression<Func<T,bool>>)Expression.Lambda(delegateType, combined); 
} 

Ваш текущий код никогда не приписывая combined:

expr => Expression.And(combined, expr); 

возвращает новый Expression, что является результатом побитовой операции AND combined и expr, но это не мутировать combined.

+0

+1 для технически замечательного ответа, спасибо. Я принял Джона, поскольку он кажется более простым, а также его использование «Где на самом деле то, что я должен делать». – gilles27

+1

@ gilles27: Да, если вы используете его только для предиката в предложении 'Where', тогда ответ Джона - это путь. Если вам нужна более общая версия, моя версия вам поможет. :-) – jason

0

Здесь у нас есть общий вопрос о объединении выражений Linq. У меня есть общее решение этой проблемы. Я дам вам ответ на конкретную проблему, хотя это определенно не способ пойти в таких случаях. Но когда простые решения проваливаются в вашем случае, вы можете попытаться использовать этот подход.

Для начала вам понадобится библиотека, состоящая из 2 простых функций. Они используют System.Linq.Expressions.ExpressionVisitor для динамического изменения выражений. Ключевой особенностью является объединение параметров внутри выражения, так что 2 параметра с тем же именем были сделаны идентичными (UnifyParametersByName). Оставшаяся часть заменяет именованный параметр заданным выражением (ReplacePar). Библиотека доступна с лицензией MIT на github: LinqExprHelper, но вы можете быстро написать что-то самостоятельно.

Библиотека позволяет использовать довольно простой синтаксис для объединения сложных выражений. Вы можете смешивать встроенные лямбда-выражения, которые приятно читать, вместе с созданием динамических выражений и композицией, которая очень способна.

private static Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions) 
    { 
     if (expressions.Count == 0) 
     { 
      return null; 
     } 

     // Prepare a master expression, used to combine other 
     // expressions. It needs more input parameters, they will 
     // be reduced later. 
     // There is a small inconvenience here: you have to use 
     // the same name "c" for the parameter in your input 
     // expressions. But it may be all done in a smarter way. 
     Expression <Func<Company, bool, bool, bool>> combiningExpr = 
      (c, expr1, expr2) => expr1 && expr2; 

     LambdaExpression combined = expressions[0]; 
     foreach (var expr in expressions.Skip(1)) 
     { 
      // ReplacePar comes from the library, it's an extension 
      // requiring `using LinqExprHelper`. 
      combined = combiningExpr 
       .ReplacePar("expr1", combined.Body) 
       .ReplacePar("expr2", expr.Body); 
     } 
     return (Expression<Func<Company, bool>>)combined; 
    } 
Смежные вопросы