5

I need to filter a list of documents by passing them to a custom filter that I'm struggling to build dynamically using a foreach loop :

var mainPredicate = PredicateBuilder.True<Document>();

// mainPredicate is combined to other filters successfully here ...

var innerPredicate = PredicateBuilder.False<Document>();
foreach (var period in periods)
{
    var p = period;
    Expression<Func<Document, bool>> inPeriod =
        d => d.Date >= p.DateFrom && d.Date <= p.DateTo;

    innerPredicate = innerPredicate.Or(d => inPeriod.Invoke(d));
}

mainPredicate = mainPredicate.And(innerPredicate);

This last line :

documents = this.ObjectSet.AsExpandable().Where(mainPredicate).ToList();

Throws this exception :

The parameter 'd' was not bound in the specified LINQ to Entities query expression.

Anyone knows why I'm getting this exception ? I don't understand where the 'd' parameter I am passing to the InPeriod method gets lost. I don't know what is missing for this to work. My code is the same as many other examples that work perfectly. Any additionnal theoric theoric information about invoking expressions and how it works behind the scenes are welcome.

Ani
  • 111,048
  • 26
  • 262
  • 307
  • What happens if you do `mainPredicate.Expand().Compile().Invoke(someDocument)`? Are you sure the problem isn't somewhere else (your code looks fine to me)? – svick May 09 '13 at 20:34
  • Given your suggestion, I modified the last line so it looks like this : `documentEntries = this.ObjectSet.AsExpandable().Where(de => mainPredicate.Expand().Compile().Invoke(de)).ToList();` but it throws : LINQ to Entities does not recognize the method 'Boolean Invoke(Remabec.PM.Tarification.DocumentEntry)' method, and this method cannot be translated into a store expression. –  May 09 '13 at 20:42
  • Yeah, that won't work and that's not what I meant. Could you try executing my code by itself? – svick May 09 '13 at 20:51
  • I changed the last line to this : `mainPredicate.Expand().Compile().Invoke(this.ObjectSet.SingleOrDefault(de => de.DocumentEntryId == 31742));` and it get executed without throwing any exception. Is that what you wanted to verify ? Do you want me to assign the result of that line to some variable ? –  May 09 '13 at 21:00
  • Yeah, that's what I meant, that's weird. – svick May 09 '13 at 21:13
  • I've also tried calling `Compile()` on mainPredicate Inside the `Where` method of my ObjectSet and the translated SQL doesn't show my filters ; it simply selects the whole ObjectSet. –  May 09 '13 at 21:40

3 Answers3

2

I don't understand why you do this:

innerPredicate = innerPredicate.Or(d => inPeriod.Invoke(d));

When you could just avoid the Invoke completely, like this:

innerPredicate = innerPredicate.Or(inPeriod);

This should work perfectly fine.


BTW, I have a feeling there's a bug with LINQKit here (unless there's some documentation that suggests that it doesn't support this scenario).

When I tried this similar code:

 Expression<Func<int, bool>> first = p1 => p1 > 4;
 Expression<Func<int, bool>> second = p2 => p2 < 2;

// Expand is similar to AsExpandable, except it works on 
// expressions, not queryables.
var composite = first.Or(d => second.Invoke(d))
                     .Expand();

...LINQKit generated the following composite expression:

p1 => ((p1 > 4) OrElse (d < 2)) // what on earth is d?

... which indeed has the unbound parameter d (NodeType = Parameter, Name = 'd').

Dodging the Invoke with first.Or(second).Expand() generates the perfectly sensible:

p1 => ((p1 > 4) OrElse (p1 < 2)) // much better now...
Ani
  • 111,048
  • 26
  • 262
  • 307
  • `innerPredicate.Or(inPeriod.Invoke)` won't compile. I think you meant `innerPredicate.Or(inPeriod)`. – svick May 10 '13 at 15:02
  • @svick: Oh, typo! I said avoid 'Invoke' then didn't delete it from the copy-pasta myself. Thanks. – Ani May 10 '13 at 15:03
  • If I don't explicitly invoke the expression (like you suggested : `innerPredicate.Or(inPeriod))` I get : __The parameter 'f' was not bound in the specified LINQ to Entities query expression.__ –  May 10 '13 at 15:14
  • Is this your whole code then? Or is there more you're not showing us? – Ani May 10 '13 at 15:15
1

Finally, I have found a way to avoid combining multiple predicates to the main expression tree.

Given that each predicate represents a different filter and I want the final, combined filter to be a series of must-be-respected conditions, we can say that each of the predicates has to return true for the final predicate to return true.

For that to work, the predicates has to be combined with AND. So, the resulting SQL query must look like this :

predicate1 AND predicate2 AND predicate3 ...

A better way to combine these predicates with AND is to chain Where query operators to the final query, like this :

var documents = this.ObjectSet.AsExpandable()
    .Where(mainPredicate)
    .Where(otherPredicate)
    .Where(yetAnotherPredicate)
    .ToList();

The resulting SQL query will combine each of these predicates with AND. That is just what I wanted to do.

It is easier than hacking out an expression tree by myself.

0

I use these extension methods:

public static class Extensions
{
    public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> source, Expression<Func<T, bool>> predicate)
    {
        InvocationExpression invokedExpression = Expression.Invoke(predicate, source.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(source.Body, invokedExpression), source.Parameters);
    }

    public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> source, Expression<Func<T, bool>> predicate)
    {
        InvocationExpression invokedExpression = Expression.Invoke(predicate, source.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(source.Body, invokedExpression), source.Parameters);
    }

}
Scott Rickman
  • 560
  • 5
  • 10