Data Platform Developer Center > Data Platform Development Forums > ADO.NET Entity Framework and LINQ to Entities > PredicateBuilder fails on nested predicates with LINQ to Entities
Ask a questionAsk a question
 

QuestionPredicateBuilder fails on nested predicates with LINQ to Entities

  • Tuesday, June 09, 2009 10:48 PMTJ Rourke Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    I'm using Joe Albahari's great PredicateBuilder ,
    but although it works great with chaining predicates together, it fails if I add a nested predicate.
    Here's what I'm trying to do:

                   var predicate = PredicateBuilder.True<Report>();
                   bool isFiltered = false;

                   if ((titlePattern != null) && (titlePattern.Length > 0))
                   {
                       predicate = predicate.And(f => f.Title.Contains(titlePattern));
                   }

                   if ((keywords != null) && (keywords.Length > 0))
                   {
                       predicate = predicate.And(Report.SummaryContains(keywords));
                    }

                    // Compile the predicate.
                    Func<Report, bool> query = predicate.Compile();

                    // Get all of the reports in the model that match the provided search terms.
                    ObjectQuery<Report> reports = _db.ReportSet;
                    return reports.AsExpandable().Where(predicate);


    And here's the generic method that checks the Summary column for any of the keywords.

            // Get an expression to determine if the Summary property contains any of the provided keywords
            public static Expression<Func<Report, bool>> SummaryContains(params string[] keywords)
            {
                Expression<Func<Report, bool>> predicate = PredicateBuilder.False<Report>();

                foreach (string keyword in keywords)
                {
                    string temp = keyword;
                    predicate = predicate.Or(f => f.Summary.Contains(temp));
                }

                return predicate;

            }

    It looks like this is suggested on Joe Albahari's page, but whenever I add the complexity like the SummaryContains method, I get the error message "The parameter 'f' was not bound in the specified LINQ to Entities query expression.".

    Any ideas what I'm doing wrong? Or is it yet another thing that doesn't work with LINQ and the Entity Framework?

    Thanks,

    Tim
    • Edited byTJ Rourke Tuesday, June 09, 2009 11:33 PM
    •  

All Replies

  • Tuesday, June 09, 2009 11:22 PMTJ Rourke Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    The answer seems to be that PredicateBuilder doesn't handle this. However, I saw another question answered by Peter Qian and referencing Colin Meek's blog .

    Colin says in his post (paraphrasing here) that LINQ to Entities is confused and doesn't see the 'f' parameter from one level of predicates as being the same 'f' parameter in the nested levels. Or at least, that's how it seems to me. He didn't really specify my case. But I tried it and PredicateBuilder worked with the complex query.

    Whee! Time to go home!

    Oh, and for anyone else who runs into this, here's what worked for me.

    1. Implement the ExpressionVisitor class found at the bottom of a referenced post on Matt Warren's blog (also a good reference for building IQueryable providers, BTW).

    NOTE: This class will require references in your source code file:
       
        using System.Collections.ObjectModel;
        using System.Linq.Expressions;

    2. Implement Colins' ParameterRebinder and Utility classes from his post at URL http://blogs.msdn.com/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx.

    NOTE: These two classes will require this reference in your source code file:
       
        using System.Linq.Expressions;

    3. In your own classes where you have a line like this:

                 var predicate = PredicateBuilder.True<Report>();
                 if ((titlePattern != null) && (titlePattern.Length > 0))
                {
                    predicate = predicate.And(f => f.Title.Contains(titlePattern));
                    isFiltered = true;
                }

    change the "predicate = predicate." bit with "predicate = Utility.", to use the visitor:

               predicate = Utility.And(predicate, Report.TitleContains(titlePattern));


    Many thanks to Colin, Matt, Joe, Webbert (who asked the question) and Peter Qian (who answered Webbert's question).
    • Edited byTJ Rourke Tuesday, June 09, 2009 11:31 PM
    • Edited byTJ Rourke Tuesday, June 09, 2009 11:29 PM
    • Edited byTJ Rourke Tuesday, June 09, 2009 11:32 PM
    •  
  • Wednesday, November 04, 2009 11:46 AMGotham Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    My hero! =O)

    You just saved me alot of time figuring this out!
    Tried it, and it worked...

    The way you put all the pieces together was perfect but linking everything to blogs and pages around the web might break in the future so I'll try to add all the code in one piece here. The links to the stuff below can be found in the previous post.
    Many thanks to all of you who made the effort!

    Add the following classes in your project:
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Linq.Expressions;
    
    namespace YourNamespaceHere
    {
    
        public static class Utility
        {
            public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
            {
                // build parameter map (from parameters of second to parameters of first)
                var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
    
                // replace parameters in the second lambda expression with parameters from the first
                var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
    
                // apply composition of lambda expression bodies to parameters from the first expression 
                return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
            }
    
            public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
            {
                return first.Compose(second, Expression.And);
            }
    
            public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
            {
                return first.Compose(second, Expression.Or);
            }
        }
    
        public class ParameterRebinder : ExpressionVisitor
        {
            private readonly Dictionary<ParameterExpression, ParameterExpression> map;
    
            public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }
    
            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }
    
            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;
                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }
                return base.VisitParameter(p);
            }
        }
    
    
        public abstract class ExpressionVisitor
        {
            protected ExpressionVisitor()
            {
            }
    
            protected virtual Expression Visit(Expression exp)
            {
                if (exp == null)
                    return exp;
                switch (exp.NodeType)
                {
                    case ExpressionType.Negate:
                    case ExpressionType.NegateChecked:
                    case ExpressionType.Not:
                    case ExpressionType.Convert:
                    case ExpressionType.ConvertChecked:
                    case ExpressionType.ArrayLength:
                    case ExpressionType.Quote:
                    case ExpressionType.TypeAs:
                        return this.VisitUnary((UnaryExpression)exp);
                    case ExpressionType.Add:
                    case ExpressionType.AddChecked:
                    case ExpressionType.Subtract:
                    case ExpressionType.SubtractChecked:
                    case ExpressionType.Multiply:
                    case ExpressionType.MultiplyChecked:
                    case ExpressionType.Divide:
                    case ExpressionType.Modulo:
                    case ExpressionType.And:
                    case ExpressionType.AndAlso:
                    case ExpressionType.Or:
                    case ExpressionType.OrElse:
                    case ExpressionType.LessThan:
                    case ExpressionType.LessThanOrEqual:
                    case ExpressionType.GreaterThan:
                    case ExpressionType.GreaterThanOrEqual:
                    case ExpressionType.Equal:
                    case ExpressionType.NotEqual:
                    case ExpressionType.Coalesce:
                    case ExpressionType.ArrayIndex:
                    case ExpressionType.RightShift:
                    case ExpressionType.LeftShift:
                    case ExpressionType.ExclusiveOr:
                        return this.VisitBinary((BinaryExpression)exp);
                    case ExpressionType.TypeIs:
                        return this.VisitTypeIs((TypeBinaryExpression)exp);
                    case ExpressionType.Conditional:
                        return this.VisitConditional((ConditionalExpression)exp);
                    case ExpressionType.Constant:
                        return this.VisitConstant((ConstantExpression)exp);
                    case ExpressionType.Parameter:
                        return this.VisitParameter((ParameterExpression)exp);
                    case ExpressionType.MemberAccess:
                        return this.VisitMemberAccess((MemberExpression)exp);
                    case ExpressionType.Call:
                        return this.VisitMethodCall((MethodCallExpression)exp);
                    case ExpressionType.Lambda:
                        return this.VisitLambda((LambdaExpression)exp);
                    case ExpressionType.New:
                        return this.VisitNew((NewExpression)exp);
                    case ExpressionType.NewArrayInit:
                    case ExpressionType.NewArrayBounds:
                        return this.VisitNewArray((NewArrayExpression)exp);
                    case ExpressionType.Invoke:
                        return this.VisitInvocation((InvocationExpression)exp);
                    case ExpressionType.MemberInit:
                        return this.VisitMemberInit((MemberInitExpression)exp);
                    case ExpressionType.ListInit:
                        return this.VisitListInit((ListInitExpression)exp);
                    default:
                        throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType));
                }
            }
    
            protected virtual MemberBinding VisitBinding(MemberBinding binding)
            {
                switch (binding.BindingType)
                {
                    case MemberBindingType.Assignment:
                        return this.VisitMemberAssignment((MemberAssignment)binding);
                    case MemberBindingType.MemberBinding:
                        return this.VisitMemberMemberBinding((MemberMemberBinding)binding);
                    case MemberBindingType.ListBinding:
                        return this.VisitMemberListBinding((MemberListBinding)binding);
                    default:
                        throw new Exception(string.Format("Unhandled binding type '{0}'", binding.BindingType));
                }
            }
    
            protected virtual ElementInit VisitElementInitializer(ElementInit initializer)
            {
                ReadOnlyCollection<Expression> arguments = this.VisitExpressionList(initializer.Arguments);
                if (arguments != initializer.Arguments)
                {
                    return Expression.ElementInit(initializer.AddMethod, arguments);
                }
                return initializer;
            }
    
            protected virtual Expression VisitUnary(UnaryExpression u)
            {
                Expression operand = this.Visit(u.Operand);
                if (operand != u.Operand)
                {
                    return Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method);
                }
                return u;
            }
    
            protected virtual Expression VisitBinary(BinaryExpression b)
            {
                Expression left = this.Visit(b.Left);
                Expression right = this.Visit(b.Right);
                Expression conversion = this.Visit(b.Conversion);
                if (left != b.Left || right != b.Right || conversion != b.Conversion)
                {
                    if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null)
                        return Expression.Coalesce(left, right, conversion as LambdaExpression);
                    else
                        return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
                }
                return b;
            }
    
            protected virtual Expression VisitTypeIs(TypeBinaryExpression b)
            {
                Expression expr = this.Visit(b.Expression);
                if (expr != b.Expression)
                {
                    return Expression.TypeIs(expr, b.TypeOperand);
                }
                return b;
            }
    
            protected virtual Expression VisitConstant(ConstantExpression c)
            {
                return c;
            }
    
            protected virtual Expression VisitConditional(ConditionalExpression c)
            {
                Expression test = this.Visit(c.Test);
                Expression ifTrue = this.Visit(c.IfTrue);
                Expression ifFalse = this.Visit(c.IfFalse);
                if (test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse)
                {
                    return Expression.Condition(test, ifTrue, ifFalse);
                }
                return c;
            }
    
            protected virtual Expression VisitParameter(ParameterExpression p)
            {
                return p;
            }
    
            protected virtual Expression VisitMemberAccess(MemberExpression m)
            {
                Expression exp = this.Visit(m.Expression);
                if (exp != m.Expression)
                {
                    return Expression.MakeMemberAccess(exp, m.Member);
                }
                return m;
            }
    
            protected virtual Expression VisitMethodCall(MethodCallExpression m)
            {
                Expression obj = this.Visit(m.Object);
                IEnumerable<Expression> args = this.VisitExpressionList(m.Arguments);
                if (obj != m.Object || args != m.Arguments)
                {
                    return Expression.Call(obj, m.Method, args);
                }
                return m;
            }
    
            protected virtual ReadOnlyCollection<Expression> VisitExpressionList(ReadOnlyCollection<Expression> original)
            {
                List<Expression> list = null;
                for (int i = 0, n = original.Count; i < n; i++)
                {
                    Expression p = this.Visit(original[i]);
                    if (list != null)
                    {
                        list.Add(p);
                    }
                    else if (p != original[i])
                    {
                        list = new List<Expression>(n);
                        for (int j = 0; j < i; j++)
                        {
                            list.Add(original[j]);
                        }
                        list.Add(p);
                    }
                }
                if (list != null)
                {
                    return list.AsReadOnly();
                }
                return original;
            }
    
            protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment assignment)
            {
                Expression e = this.Visit(assignment.Expression);
                if (e != assignment.Expression)
                {
                    return Expression.Bind(assignment.Member, e);
                }
                return assignment;
            }
    
            protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding)
            {
                IEnumerable<MemberBinding> bindings = this.VisitBindingList(binding.Bindings);
                if (bindings != binding.Bindings)
                {
                    return Expression.MemberBind(binding.Member, bindings);
                }
                return binding;
            }
    
            protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding binding)
            {
                IEnumerable<ElementInit> initializers = this.VisitElementInitializerList(binding.Initializers);
                if (initializers != binding.Initializers)
                {
                    return Expression.ListBind(binding.Member, initializers);
                }
                return binding;
            }
    
            protected virtual IEnumerable<MemberBinding> VisitBindingList(ReadOnlyCollection<MemberBinding> original)
            {
                List<MemberBinding> list = null;
                for (int i = 0, n = original.Count; i < n; i++)
                {
                    MemberBinding b = this.VisitBinding(original[i]);
                    if (list != null)
                    {
                        list.Add(b);
                    }
                    else if (b != original[i])
                    {
                        list = new List<MemberBinding>(n);
                        for (int j = 0; j < i; j++)
                        {
                            list.Add(original[j]);
                        }
                        list.Add(b);
                    }
                }
                if (list != null)
                    return list;
                return original;
            }
    
            protected virtual IEnumerable<ElementInit> VisitElementInitializerList(ReadOnlyCollection<ElementInit> original)
            {
                List<ElementInit> list = null;
                for (int i = 0, n = original.Count; i < n; i++)
                {
                    ElementInit init = this.VisitElementInitializer(original[i]);
                    if (list != null)
                    {
                        list.Add(init);
                    }
                    else if (init != original[i])
                    {
                        list = new List<ElementInit>(n);
                        for (int j = 0; j < i; j++)
                        {
                            list.Add(original[j]);
                        }
                        list.Add(init);
                    }
                }
                if (list != null)
                    return list;
                return original;
            }
    
            protected virtual Expression VisitLambda(LambdaExpression lambda)
            {
                Expression body = this.Visit(lambda.Body);
                if (body != lambda.Body)
                {
                    return Expression.Lambda(lambda.Type, body, lambda.Parameters);
                }
                return lambda;
            }
    
            protected virtual NewExpression VisitNew(NewExpression nex)
            {
                IEnumerable<Expression> args = this.VisitExpressionList(nex.Arguments);
                if (args != nex.Arguments)
                {
                    if (nex.Members != null)
                        return Expression.New(nex.Constructor, args, nex.Members);
                    else
                        return Expression.New(nex.Constructor, args);
                }
                return nex;
            }
    
            protected virtual Expression VisitMemberInit(MemberInitExpression init)
            {
                NewExpression n = this.VisitNew(init.NewExpression);
                IEnumerable<MemberBinding> bindings = this.VisitBindingList(init.Bindings);
                if (n != init.NewExpression || bindings != init.Bindings)
                {
                    return Expression.MemberInit(n, bindings);
                }
                return init;
            }
    
            protected virtual Expression VisitListInit(ListInitExpression init)
            {
                NewExpression n = this.VisitNew(init.NewExpression);
                IEnumerable<ElementInit> initializers = this.VisitElementInitializerList(init.Initializers);
                if (n != init.NewExpression || initializers != init.Initializers)
                {
                    return Expression.ListInit(n, initializers);
                }
                return init;
            }
    
            protected virtual Expression VisitNewArray(NewArrayExpression na)
            {
                IEnumerable<Expression> exprs = this.VisitExpressionList(na.Expressions);
                if (exprs != na.Expressions)
                {
                    if (na.NodeType == ExpressionType.NewArrayInit)
                    {
                        return Expression.NewArrayInit(na.Type.GetElementType(), exprs);
                    }
                    else
                    {
                        return Expression.NewArrayBounds(na.Type.GetElementType(), exprs);
                    }
                }
                return na;
            }
    
            protected virtual Expression VisitInvocation(InvocationExpression iv)
            {
                IEnumerable<Expression> args = this.VisitExpressionList(iv.Arguments);
                Expression expr = this.Visit(iv.Expression);
                if (args != iv.Arguments || expr != iv.Expression)
                {
                    return Expression.Invoke(expr, args);
                }
                return iv;
            }
        }
    }
    

    And instead of:
    var inner = PredicateBuilder.False<Product>();
    inner = inner.Or (p => p.Description.Contains ("foo"));
    inner = inner.Or (p => p.Description.Contains ("far"));
    
    var outer = PredicateBuilder.True<Product>();
    outer = outer.And (p => p.Price > 100);
    outer = outer.And (p => p.Price < 1000);
    outer = outer.And (inner);
    
    write:
    var inner = PredicateBuilder.False<Product>();
    inner = YourNamespaceHere.Utility.Or (inner, p => p.Description.Contains ("foo"));
    inner = YourNamespaceHere.Utility.Or (inner, p => p.Description.Contains ("far"));
    
    var outer = PredicateBuilder.True<Product>();
    outer = YourNamespaceHere.Utility.And (outer, p => p.Price > 100);
    outer = YourNamespaceHere.Utility.And (outer, p => p.Price < 1000);
    outer = YourNamespaceHere.Utility.And (outer, inner);
    

    Kind regards
    Richard