2009-12-17 49 views
18

我有一個表單上有多個字段(公司名稱,郵編等),它允許用戶搜索數據庫中的公司。如果用戶在多個字段中輸入值,那麼我需要搜索所有這些字段。我正在使用LINQ來查詢數據庫。如何將LINQ表達式合併爲一個?

到目前爲止,我已經設法編寫一個函數,它將查看它們的輸入並將其轉換爲表達式列表。我現在想把這個List變成一個單一的表達式,然後我可以通過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; 
    } 

但是這個失敗的線沿線的異常消息「二元運算符,而不是用於定義...」。有沒有人有任何想法我需要做的結合這些表達式?

編輯:更正了我忘記將結果和表達式一起分配給一個變量的行。感謝您指出這些人。

回答

9

編輯:傑森的答案現在比我的表達樹的東西更充分,所以我已經刪除了那一點。然而,我想離開這個:

我假設你正在使用這些爲Where條款...爲什麼不只是調用每個表達式依次在哪裏?這應該有同樣的效果:

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

@Jon Skeet:'combined'將被鍵入爲'Expression';你需要做一些工作來將它作爲'Expression >'返回。 – jason 2009-12-17 15:58:18

+0

我同意你的第一個代碼更容易理解,所以我會做出這個正確的答案。但是我實際上要使用第二個片段,因爲這正是我需要的 - 我讓事情太複雜,謝謝喬恩。 – gilles27 2009-12-17 15:59:17

+1

具有諷刺意味的是,我在編輯這兩條評論的同時也進行了編輯 - 但是因爲這是使用的第二個片段,所以我認爲我會保留原樣:) – 2009-12-17 16:00:24

22

您可以使用Enumerable.AggregateExpression.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是按位安定combinedexpr的結果,但它並沒有發生變異combined

+0

對於一個技術上很好的答案+1,謝謝。我已經接受了Jon,因爲它看起來更簡單,而且他使用Where實際上是我應該做的。 – gilles27 2009-12-17 16:00:39

+1

@ gilles27:是的,如果你只是將它用於Where子句中的謂詞,那麼Jon的答案就是要走的路。如果您需要更通用的版本,我的版本將幫助您。 :-) – jason 2009-12-17 16:03:01

0

這裏我們有一個關於組合Linq表達式的一般問題。我有這個問題的一般解決方案。我會針對所發佈的具體問題提供答案,但在這種情況下絕對不是這樣。但是,如果簡單的解決方案在您的情況下失敗,您可以嘗試使用這種方法。

首先,您需要一個由2個簡單功能組成的庫。他們使用System.Linq.Expressions.ExpressionVisitor來動態修改表達式。關鍵特性是將表達式中的參數統一起來,以便使具有相同名稱的兩個參數相同(UnifyParametersByName)。剩下的部分將用給定的表達式(ReplacePar)替換命名參數。該圖書館可在github上獲得MIT許可證:LinqExprHelper,但您可以自行快速編寫一些內容。

該庫允許用於組合複雜表達式的相當簡單的語法。您可以混合內聯的lambda表達式,這些表達式非常適合閱讀,以及動態表達式創建和組合,這非常有用。

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; 
    } 
相關問題