Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this code with PredicateBuilder not working?

Tags:

c#

linq

Why is my list not returning anything?

 class Program
    {
        static void Main(string[] args)
        {
            var list = new List<string>{ "apple" , "mango" , "chickoo", "kiwi" };
            var searchWord = new List<string>{ "a" };

            var predicate = PredicateBuilder.False<string>();

            foreach(var word in searchWord)
            {
                predicate.Or(p => p.Contains(word));
            }

           var qry = list.Where(predicate.Compile());

           foreach (var item in qry)
           {
               Console.WriteLine(item);
           }

           Console.Read();
        }
    }

I am using Joseph Albahari's PredicateBuilder.

like image 511
TCM Avatar asked Mar 14 '11 17:03

TCM


3 Answers

You need to assign the result to your predicate variable:

predicate = predicate.Or(p => p.Contains(word));

The PredicateBuilder page also emphasizes an important issue:

The temporary variable in the loop is required to avoid the outer variable trap, where the same variable is captured for each iteration of the foreach loop.

Thus, your code should resemble this:

        foreach(var word in searchWord)
        {
            var temp = word;
            predicate = predicate.Or(p => p.Contains(temp));
        }
like image 102
Ahmad Mageed Avatar answered Oct 07 '22 00:10

Ahmad Mageed


There are two issues here:

  1. The Or extension-method does not mutate an existing expression-tree - it returns a new one. Expression-trees in general are immutable.
  2. You are closing over the loop variable. To understand why this is a problem, see Closing over the loop variable considered harmful.

Try:

foreach(var word in searchWord)
{
    var wordCopy = word;
    predicate = predicate.Or(p => p.Contains(wordCopy));
}

Or even nicer:

var predicate = searchWord.Aggregate(PredicateBuilder.False<string>(),
                   (predSoFar, word) => predSoFar.Or(p => p.Contains(word)));
like image 34
Ani Avatar answered Oct 06 '22 22:10

Ani


This doesn't look right to me:

foreach(var word in searchWord)
{
    predicate.Or(p => p.Contains(word));
}

I suspect it should be:

foreach(var word in searchWord)
{
    predicate = predicate.Or(p => p.Contains(word));
}

In other words, just like the rest of LINQ, PredicateBuilder doesn't let you change the current predicate - it lets you build a new predicate based on the existing one and a new condition.

That's certainly what the sample code suggests, anyway...

When you look at the complete source code for PredicateBuilder (on the same page), that confirms it - the predicate is actually just an Expression<Func<T, bool>> - i.e. an expression tree. You're not creating an instance of a PredicateBuilder class or anything like that. Expression trees are immutable, so the only thing Or can do is return a new expression tree, like this:

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);
}
like image 41
Jon Skeet Avatar answered Oct 06 '22 22:10

Jon Skeet