1

这是问题的一个例子:

   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 构建器不同,我发现使用它有点不安全。

现在有几个问题:

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 表达式结果中看不到 Invokes,所以它们似乎没用......

3)有没有其他方法来构造表达式“离线”,然后传递给默认的 Linq 表达式构建器?像这样的东西:

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

然后 Linq Expression builder 解析它并创建与 x => x.Id == 1 || 相同的表达式 x.Id == 3 && x.Id > 2 (给予 && 更高的优先级)?或者也许我可以调整 PredicateBuilder 来做同样的事情?

4

1 回答 1

3

扩展我上面的评论:

因为这里没有运算符优先级的概念。您实际上是在自己构建表达式树,并将一种方法的结果“管道”到下一种方法确定顺序。因此,结果表达式的顺序将与您指定的完全一致。

完整的源代码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手工构建是一个循序渐进的过程,因此您必须确保将每个步骤分解为正确的顺序以获得您想要/需要的结果。

于 2013-01-23T16:54:26.883 回答