下面是问题的示例:
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 表达式生成器不同,则使用它有点不安全。
现在一些问题:
1) 为什么 Linq 要创建这些组?即使我创建了一个 Or 表达式
x => x.Id == 1 || x.Id == 3 || x.Id > 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 表达式结果中没有看到调用,所以它们似乎没用......
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来做同样的事情?
扩展我上面的评论:
因为它在这里没有运算符优先级的概念。你是 从字面上看,自己构建表达式树,并"管道"了 从一种方法到下一个方法的结果决定了顺序。因此,顺序 生成的表达式将完全符合您指定的表达式。
PredicateBuilder
的完整来源发布在这里,并显示了它是多么简单。但它也显示了上述问题的根源。如果您不想访问Albahari的网站,以下是完整的来源:
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
在很大程度上是一个循序渐进的过程,因此您必须确保将每个步骤分解为正确的顺序,以获得您想要/需要的结果。