2013-01-23 22 views
1

這是問題的一個例子:LINQ的PredicateBuilder,分組和運算符優先級

var source = new LambdasTestEntity[] { 
     new LambdasTestEntity {Id = 1}, 
     new LambdasTestEntity {Id = 2},   
     new LambdasTestEntity {Id = 3}, 
     new LambdasTestEntity {Id = 4},   
    }; 

    Expression<Func<LambdasTestEntity, bool>> expression1 = x => x.Id == 1; 
    Expression<Func<LambdasTestEntity, bool>> expression2 = x => x.Id == 3; 
    Expression<Func<LambdasTestEntity, bool>> expression3 = x => x.Id > 2; 

    // try to chain them together in a following rule 
    // Id == 1 || Id == 3 && Id > 2 
    // as && has higher precedence, we expect getting two entities 
    // with Id=1 and Id=3 

    // see how default LINQ works first 
    Expression<Func<LambdasTestEntity, bool>> expressionFull = x => x.Id == 1 || x.Id == 3 && x.Id > 2; 

    var filteredDefault = source.AsQueryable<LambdasTestEntity>() 
       .Where(expressionFull).ToList(); 

    Assert.AreEqual(2, filteredDefault.Count); // <-this passes 

    // now create a chain with predicate builder 
    var totalLambda = expression1.Or(expression2).And(expression3); 

    var filteredChained = source.AsQueryable<LambdasTestEntity>() 
       .Where(totalLambda).ToList(); 


    Assert.AreEqual(2, filteredChained.Count); 
    // <- this fails, because PredicateBuilder has regrouped the first expression, 
    // so it now looks like this: (Id == 1 || Id == 3) && Id > 2 

當我在手錶找兩個表達式,我看到以下內容:

expressionFull as it is coming from Linq: 
(x.Id == 1) OrElse ((x.Id == 3) AndAlso (x.Id > 2)) 

totalLambda for PredicateBuilder: 
((x.Id == 1) OrElse Invoke(x => (x.Id == 3), x)) AndAlso Invoke(x => (x.Id > 2), x) 

我發現這是如果它的行爲與默認的Linq Expression構建器不同,則使用PredicateBuilder會有點不安全。

現在的一些問題:

1)爲什麼LINQ的創建這些組?即使我創建一個或者表達

我還是老樣子得到分組這樣的前兩個標準:

((x.Id == 1) OrElse (x.Id == 3)) OrElse (x.Id > 2) 

爲什麼它不只是

(x.Id == 1) OrElse (x.Id == 3) OrElse (x.Id > 2) 

2)爲什麼PredicateBuilder正在添加這些Invokes?我沒有看到在默認的Linq表達式結果中的調用,所以他們似乎沒用...

3)是否有任何其他方式來構建表達式「脫機」,然後傳遞給默認的Linq Expression構建器?是這樣的:

ex = x => x.Id == 1; 
ex = ex || x.Id == 3; 
ex = ex && x.Id > 2; 

然後LINQ表達式構建器然後解析它並創建相同的表達因爲它對於x => x.Id == 1 || x.Id == 3 & & x.Id> 2(給予& &更高的優先級)? 或者我可以調整PredicateBuilder來做同樣的事情?

+0

它不是因爲'&&'對c中的'||'具有優先權,所以擴展方法'And'在PredicateBuilder中的擴展方法'Or'具有優先權。你可以做'expression1.Or(expression2.And(expression3));'但是我不知道這是你想要的,還是如果它的工作) –

+0

而這正是問題 - C#和默認的Linq表達式生成器都給&&更高的優先級,但PredicateBuilder.And沒有。有時候可能會讓人困惑。 – JustAMartin

+0

那麼,對於(我至少認爲)大多數人來說,運算符優先級的使用不像可靠的(或「第一眼」可以理解的)比好的舊括號...因此,帶括號的解決方案對你來說並不合適? –

回答

3

擴大對我的評論上面:

,因爲它沒有運算符優先級的概念在這裏。你自己從字面上構建表達式樹,並將一個方法的結果「管道」到下一個確定順序。因此,結果表達式的順序將與您指定的完全相同。

PredicateBuilder的完整源郵件已發佈here並顯示出它的簡單程度。但它也顯示了你上面問題的根源。如果你不想訪問阿爾巴哈利的網站,這裏有完整的源:

public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1, 
               Expression<Func<T, bool>> expr2) 
{ 
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression>()); 
    return Expression.Lambda<Func<T, bool>> 
     (Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters); 
} 

public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1, 
                Expression<Func<T, bool>> expr2) 
{ 
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression>()); 
    return Expression.Lambda<Func<T, bool>> 
     (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters); 
} 

主要的事情這裏需要注意的是,它構建了表達一個節點的時間,然後通過管道將這個節點作爲左表達式(葉)的後續節點。調用Expression.Invoke只是簡單地將參數從現有節點傳遞到右側葉子(下一個表達式),其餘部分非常明顯。

編輯:我不得不做一些類似的事情(但沒有使用PredicateBuilder,使用Expression調用自己構建樹)。要記住的主要問題是,您只需首先處理And/AndAlso節點,然後再處理Or/OrElse節點,這樣您就可以構建具有適當優先級的樹。不幸的是,手動構建ExpressionTrees是一個非常分步的過程,因此您必須確保將每個步驟按照正確的順序分解,以獲得您想要的結果。

+0

謝謝,現在我明白爲什麼會發生這種分組 - 因爲邏輯操作是作爲二叉樹處理的,其中每個操作總是具有左/右側。我還發現PredicateBuilder的替代方法:http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx它似乎有點複雜,但它確實不使用Invokes,所以它可能更普遍,我猜。 – JustAMartin

+1

這不僅限於二進制操作,它是如何表示所有表達式。請參閱[此可視化文件](http://www.manuelabadia.com/blog/PermaLink,guid,9160035f-490f-46bd-ab55-516b5c7545af.aspx)以查看Expression如何分解爲樹的每個節點。 – SPFiredrake