Thursday, October 20, 2011

LINQ to Entities: PredicateBuilder and Dynamic Linq

When you want to build dynamic filter for a query on LINQ to SQL or LINQ to Entities (Entity Framework, NHibernate, ..), you can use a famous Albahari's PredicateBuilder. But sometime, it may be doesn't work perfect with LINQ to Entities. To solve this problem, Pete Montgomery introduced a universal PredicateBuilder that combined the original version of Albahari's PredicateBuilder and LINQ to Entities: Combining Predicates.

The source code of this PredicateBuilder as following:
/// <summary>
/// Enables the efficient, dynamic composition of query predicates.
/// </summary>
public static class PredicateBuilder
{
    /// <summary>
    /// Creates a predicate that evaluates to true.
    /// </summary>
    public static Expression<Func<T, bool>> True<T>()
    {
        return param => true;
    }

    /// <summary>
    /// Creates a predicate that evaluates to false.
    /// </summary>
    public static Expression<Func<T, bool>> False<T>()
    {
        return param => false;
    }

    /// <summary>
    /// Creates a predicate expression from the specified lambda expression.
    /// </summary>
    public static Expression<Func<T, bool>> Create<T>(
        Expression<Func<T, bool>> predicate)
    {
        return predicate;
    }

    /// <summary>
    /// Combines the first predicate with the second using the logical "and".
    /// </summary>
    public static Expression<Func<T, bool>> And<T>(
        this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.AndAlso);
    }

    /// <summary>
    /// Combines the first predicate with the second using the logical "or".
    /// </summary>
    public static Expression<Func<T, bool>> Or<T>(
        this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.OrElse);
    }

    /// <summary>
    /// Negates the predicate.
    /// </summary>
    public static Expression<Func<T, bool>> Not<T>(
        this Expression<Func<T, bool>> expression)
    {
        var negated = Expression.Not(expression.Body);
        return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
    }

    /// <summary>
    /// Combines the first expression with the second using the specified merge function.
    /// </summary>
    static Expression<T> Compose<T>(
        this Expression<T> first, Expression<T> second, Func<Expression, 
        Expression, Expression> merge)
    {
        // zip parameters (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 the parameters in the first
        var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

        // create a merged lambda expression with parameters from the first expression
        return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
    }

    class ParameterRebinder : ExpressionVisitor
    {
        readonly Dictionary<ParameterExpression, ParameterExpression> _map;

        ParameterRebinder(Dictionary<ParameterExpression, 
            ParameterExpression> map)
        {
            _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);
        }
    }
}

But please stay on, we have a problem if we use this PredicateBulder with dynamic filter that is same as (A OR B) AND (X OR Y).
When you try to build this filter, at run-time, you it throws an exception message as following:
The parameter 'f' was not bound in the specified LINQ to Entities query expression.
To solve the above issue, we must use Expand method to combine between PredicateBuilder expressions.
And here is the sample for this problem:

Sample in SQL syntax:
SELECT * 
FROM dbo.Customers c
WHERE 
    (c.FirstName LIKE '%A%' 
    OR c.LastName LIKE '%B%')
AND (c.Birthday = '1998-01-01' 
    OR c.Birthday = '1999-12-31')

And here is in C# Statements:

using (var db = new CustomerContext())
{
    //With OR block, you must use FALSE
    var wBlockOr1 = PredicateBuilder.False();

    wBlockOr1 = wBlockOr1.Or(c => c.FirstName.Contains("A"));
    wBlockOr1 = wBlockOr1.Or(c => c.LastName.Contains("B"));

    var wBlockOr2 = PredicateBuilder.False();

    wBlockOr1 = wBlockOr1.Or(
        c => c.Birthday == new DateTime(1998, 1, 1));
    wBlockOr1 = wBlockOr1.Or(
        c => c.Birthday == new DateTime(1999, 12, 31));

    //With AND block, you must use TRUE
    var wAndBlock = PredicateBuilder.True();

    //Combine between Or blocks
    wAndBlock = wAndBlock.And(wBlockOr1.Expand());
    wAndBlock = wAndBlock.And(wBlockOr2.Expand());

    var customers = db.Customers.AsExpandable().Where(wAndBlock);
}

Hope that this article help you to solve the problem on LINQ to Entities.

No comments:

Post a Comment