0

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().

John Ohara
  • 2,821
  • 3
  • 28
  • 54

3 Answers3

1

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());
}
Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • `Invoke` is not really needed because LinqKit includes a PredicateBuilder with `And` / `Or` extension methods (as you know :) – Ivan Stoev Nov 18 '16 at 11:03
  • @IvanStoev, actually I never knew about the `PredicateBuilder` in LinqKit before. Thanks for letting me know. Can it be used in this case if the parameters in the two expressions are different? – Yacoub Massad Nov 18 '16 at 11:08
  • It still has to be combined with `Expand` / `AsExpandable`. – Ivan Stoev Nov 18 '16 at 11:10
0

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);
Anderson Pimentel
  • 5,086
  • 2
  • 32
  • 54
  • 1
    Unfortunately this doesn't work because the 2 expression bodies are bound to a different parameter. – Ivan Stoev Nov 18 '16 at 11:02
  • I'm not sute if the output is correct for this but ir does compile. – John Ohara Nov 18 '16 at 11:05
  • I have a very similar code working in one my projects. In fact, in my code I combine the parameters from the two expressions before using them: `left.Parameters.Concat(right.Parameters).GroupBy(p => p.Name).Select(g => g.First())` – Anderson Pimentel Nov 18 '16 at 11:08
  • @JohnOhara In LINQ to Entities (and in general with expressions, reflection etc) compile doesn't mean works :) – Ivan Stoev Nov 18 '16 at 11:09
  • Never used LINQ to Entities. It works fine with NHibernate. To be honest, one little thing must be changed: `exp = x => x.IsLive` should be `exp = x => x.IsLive == true`. NH tends to be kinda lost with simple boolean expressions. =) – Anderson Pimentel Nov 18 '16 at 11:11
  • @IvanStoev Just tested here: works fine with NHibernate, but does not with Linq To Objects. I think NH LINQ Provider only cares about the parameter name. – Anderson Pimentel Nov 18 '16 at 11:19
  • 1
    EF does care, trust me :) To make it work, you need a simple parameter replacer. – Ivan Stoev Nov 18 '16 at 11:38
0

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));
}
Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343