I am using C# (including Linq) to develop a web application. I have written a generic method to extend Get method of any entity. However when I get the runtime exception 'The LINQ expression node type 'Invoke' is not supported in LINQ to Entities' when the code is executed. Below is the code:
using System.Linq;
using System.Linq.Expressions;
using LinqKit;
public static class ServiceExtension
{
public static IEnumerable<T> GetActive<T>(this ICrudService<T> crudService, Expression<Func<T, bool>> where)
where T : class, IDeletable
{
return crudService.Get(where.And(w => !w.IsDeleted));
}
}
Can someone please tell me what I am doing wrong?
You're using LinqKit, which will only work on queryables that have had AsExpandable()
called on them. This will wrap the underlying query provider and translate all calls to Invoke
(which And
is using internally) into something that the query provider will understand.
The alternative would be to simply not use LinqKit, and use the following version of PredicateBuilder that can And/Or predicate expressions without relying on the use of Invoke
:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
It instead relies on the following method to replace all instance of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Servy's answer is great and has been very useful for me. I've taken it and expanded/changed it slightly, and will add it onto this to pay back a little bit.
First off, I renamed the True and False properties to be BaseAnd (instead of True) and BaseOr (instead of false). Mainly this was to be more understandable to me based on how I use them in order to get the results I desire.
In addition, I added two new generic functions: AddToPredicateTypeBasedOnIfAndOrOr, which takes two ref predicates, one for ands and one for ors and will add an expression onto one of them depending on if it's supposed to be an and or not. This is just to reduce code duplication as my code doesn't know which type it's supposed to be before the app runs.
CombineOrPreicatesWithAndPredicates takes an initial predicate expression, an and predicate expression and an or predicate expression and combine them in a sql logical way, (and list) and (or list). This is also to reduce code duplication.
Hope this helps someone out there.
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> BaseAnd<T>() { return f => true; }
public static Expression<Func<T, bool>> BaseOr<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
public static Expression<Func<T, bool>> CombineOrPreicatesWithAndPredicates<T>(this Expression<Func<T, bool>> combinedPredicate,
Expression<Func<T, bool>> andPredicate, Expression<Func<T, bool>> orPredicate)
{
combinedPredicate = combinedPredicate ?? BaseAnd<T>();
if (andPredicate != null && orPredicate!=null)
{
andPredicate = andPredicate.And(orPredicate);
combinedPredicate = combinedPredicate.And(andPredicate);
}
else if (orPredicate!=null)
{
combinedPredicate = combinedPredicate.And(orPredicate);
}
else
{
combinedPredicate = combinedPredicate.And(andPredicate);
}
return combinedPredicate;
}
public static void AddToPredicateTypeBasedOnIfAndOrOr<T>(ref Expression<Func<T, bool>> andPredicate,
ref Expression<Func<T, bool>> orPredicate, Expression<Func<T, bool>> newExpression, bool isAnd)
{
if (isAnd)
{
andPredicate = andPredicate ?? BaseAnd<T>();
andPredicate = andPredicate.And(newExpression);
}
else
{
orPredicate = orPredicate ?? BaseOr<T>();
orPredicate = orPredicate.Or(newExpression);
}
}
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
I beleive the problem is in your And method. You can create the expression yourself using the static Expression methods
public static IEnumerable<T> GetActive<T>(this ICrudService<T> crudService, Expression<Func<T, bool>> where)
where T : class, IDeletable
{
var parameter = where.Parameters.FirstOrDefault();
var property = Expression.PropertyOrField(parameter, "IsDeleted");
var notProperty = Expression.Not(property);
var andExpression = Expression.AndAlso(where.Body, notProperty);
var lambda = Expression.Lambda<Func<T, bool>>(andExpression, parameter);
return crudService.Get(lambda);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With