Reputation: 2901
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
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
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
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