John Ohara
John Ohara

Reputation: 2901

Trying to combine 2 linq expressions

I have some queries over an Entity Framework model and am trying to be clever about consolidating my queries.

I already have a generic repository.

Here's the crux of what I'm trying to do - it doesn't work but I'm not sure why and how to fix it.

The second function in code works but there are a couple of issues in the first - I've included it so that you can get the gist of what I'm trying to do.

    IEnumerable<Product> QueryLive(Expression<Func<Product, bool>> predicate)
    {
        var exp = new Expression<Func<Product, bool>>(x => x.IsLive);

        var combined = Expression.AndAlso(exp, predicate);

        return QueryPublished(combined);
    }

    IEnumerable<Product> QueryPublished(Expression<Func<Product, bool>> predicate)
    {
        using (var uow = new UnitOfWork(Connections.ProductComparision))
        {
            var r = new Repository<Product>(uow.Context);

            return r.Find(predicate).ToList();
        }
    }

I get 2 syntax errors:

Line: var exp = 'System.Linq.Expressions.Expression' does not contain a constructor that takes 1 arguments

Return: 'combined' var is a binary expression and conflicts with the param requirements of QueryPublished().

Upvotes: 0

Views: 1610

Answers (3)

Ivan Stoev
Ivan Stoev

Reputation: 205849

Most of the Expression class methods are for building the expression trees (e.g. the lambda expression Body and cannot be used directly for combining lambda expressions.

For combining the so called predicate expressions (Expression<Func<T, bool>>) as in your case, one usually would use some custom helper extension methods, called predicate builders. The most famous is the PredicateBuilder from LinqKit, but it's not compatible with EF and requires Expand / AsExpandable` services from the package. I personally use my own, posted here Applying LINQ filters based on a multi-dimensional array and Establish a link between two lists in linq to entities where clause, which as been pointed in comments by Gert Arnold is very similar to the universal PredicateBuilder. Both produce EF compatible expressions, here is mine:

public static class PredicateUtils
{
    sealed class Predicate<T>
    {
        public static readonly Expression<Func<T, bool>> True = item => true;
        public static readonly Expression<Func<T, bool>> False = item => false;
    }
    public static Expression<Func<T, bool>> Null<T>() { return null; }
    public static Expression<Func<T, bool>> True<T>() { return Predicate<T>.True; }
    public static Expression<Func<T, bool>> False<T>() { return Predicate<T>.False; }
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
    {
        if (Equals(left, right)) return left;
        if (left == null || Equals(left, True<T>())) return right;
        if (right == null || Equals(right, True<T>())) return left;
        if (Equals(left, False<T>()) || Equals(right, False<T>())) return False<T>();
        var body = Expression.AndAlso(left.Body, right.Body.Replace(right.Parameters[0], left.Parameters[0]));
        return Expression.Lambda<Func<T, bool>>(body, left.Parameters);
    }
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
    {
        if (Equals(left, right)) return left;
        if (left == null || Equals(left, False<T>())) return right;
        if (right == null || Equals(right, False<T>())) return left;
        if (Equals(left, True<T>()) || Equals(right, True<T>())) return True<T>();
        var body = Expression.OrElse(left.Body, right.Body.Replace(right.Parameters[0], left.Parameters[0]));
        return Expression.Lambda<Func<T, bool>>(body, left.Parameters);
    }

    static Expression Replace(this Expression expression, Expression source, Expression target)
    {
        return new ExpressionReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ExpressionReplacer : ExpressionVisitor
    {
        public Expression Source;
        public Expression Target;
        public override Expression Visit(Expression node)
        {
            return node == Source ? Target : base.Visit(node);
        }
    }
}

Once you have such helpers, the method in question is simple as that:

IEnumerable<Product> QueryLive(Expression<Func<Product, bool>> predicate)
{
    return QueryPublished(predicate.And(x => x.IsLive));
}

Upvotes: 0

Anderson Pimentel
Anderson Pimentel

Reputation: 5787

First:

Change

var exp = new Expression<Func<Product, bool>>(x => x.IsLive);

To

Expression<Func<Product, bool>> exp = x => x.IsLive;

Second:

Change

var combined = Expression.AndAlso(exp, predicate);

To

var combined = Expression.Lambda<Func<Product, bool>>(Expression.AndAlso(exp.Body, predicate.Body), exp.Parameters);

Upvotes: 0

Yacoub Massad
Yacoub Massad

Reputation: 27871

Here are some issues that I see with your code:

  • var exp = new Expression<Func<Product, bool>>(x => x.IsLive); will not compile, to define an expression, simple use the following syntax:

    Expression<Func<Product, bool>> exp = x => x.IsLive;

  • combined is of type Expression and QueryPublished is expecting an argument of type Expression<Func<Product, bool>>. This wouldn't compile also.

  • The parameter in predicate (e.g. the x in x =>) is different than the lambda parameter exp, so you can't simply combine them this way.

You can use LinqKit to combine expressions easily like this:

IEnumerable<Product> QueryLive(Expression<Func<Product, bool>> predicate)
{
    Expression<Func<Product, bool>> exp = x => x.IsLive;

    Expression<Func<Product, bool>> combined = x => exp.Invoke(x) && predicate.Invoke(x);

    return QueryPublished(combined.Expand());
}

Upvotes: 1

Related Questions