問題の例を次に示します。
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
両方の式の Watches を調べると、次のように表示されます。
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)
PredicateBuilder の動作がデフォルトの Linq Expression Builder と異なる場合、PredicateBuilder を使用するのは少し安全ではないと思います。
ここでいくつかの質問:
1) Linq がこれらのグループを作成するのはなぜですか? Or式を作っても
x => x.Id == 1 || x.Id == 3 || x.Id > 2
最初の 2 つの基準は、次のようにグループ化されています。
((x.Id == 1) OrElse (x.Id == 3)) OrElse (x.Id > 2)
なぜそれだけではないのか
(x.Id == 1) OrElse (x.Id == 3) OrElse (x.Id > 2)
?
2) PredicateBuilder がこれらの呼び出しを追加するのはなぜですか? デフォルトのLinq式の結果にInvokesが表示されないため、役に立たないようです...
3) 式を「オフライン」で構築し、デフォルトの Linq 式ビルダーに渡す他の方法はありますか? このようなもの:
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 を微調整して同じことを行うことはできますか?