Question

Here is an example of the problem:

   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

When I look in Watches for both expressions, I see the following:

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)

I find it is a bit unsafe to use the PredicateBuilder if it behaves differently from default Linq Expression builder.

Now some questions:

1) Why is Linq creating those groups? Even if I create an Or expression

x => x.Id == 1 || x.Id == 3 || x.Id > 2

I stil get the first two criteria grouped like this:

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

Why it is not just

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

?

2) Why PredicateBuilder is adding those Invokes? I don't see Invokes in the default Linq expression result, so they seem useless...

3) Is there any other way to construct the expression "offline" and then pass to the default Linq Expression builder? Something like this:

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

and then Linq Expression builder then parses it and creates the same expression as it does for x => x.Id == 1 || x.Id == 3 && x.Id > 2 (giving && higher precedence)? Or maybe I could tweak the PredicateBuilder to do the same?

Était-ce utile?

La solution

Expanding on my comment above:

Because it has no concept of operator precedence here. You're literally building the expression tree yourself, and "piping" the results of one method to the next determines order. Thus, the order of the resulting expression is going to be exactly what you specified.

The complete source for PredicateBuilder is posted here and shows just how simple it is. But it also shows you the source of your problem above. In case you don't want to visit Albahari's site, here's the complete source:

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);
}

The main thing to notice here is that it builds the expression one node at a time, then pipes this node as being the left expression (leaf) of the subsequent node. The Expression.Invoke call is simply to pipe the parameters from your existing node into the right leaf (the next expression), and the rest is pretty self explanatory.

Edit: I had to do something similar to this (but didn't use PredicateBuilder, built the trees myself using Expression calls). The main thing to keep in mind is that you just have to process the And/AndAlso nodes first, and then handle the Or/OrElse nodes, that way you build the tree with the proper precedence. Unfortunately, building ExpressionTrees by hand are very much a step-by-step process, so you have to make sure you break each of the steps down into the correct order to get the results you want/need.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top