9

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?

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
COBOL
  • 1,031
  • 8
  • 16
  • 32
  • 1
    Could you show us what the `Get`-method does? – Kenneth Mar 14 '14 at 13:55
  • A example of a call that I am doing is `Service.GetActive(x => x.Id > 10)`. Am I possible calling it incorrectly? – COBOL Mar 14 '14 at 13:56
  • There's nothing here about `Invoke`. The error is about another line. Maybe in the `Get` method or the `where` expression? – Alberto Solano Mar 14 '14 at 13:56
  • Have you considered that Linq to Entities does not support Invoke? I think the error message is exactly correct. Linq to SQL does support it but not Linq to Entities. You must be calling invoke somewhere – clhereistian Mar 14 '14 at 13:57
  • What does the `And` method do? Is that a custom method you created? – Aducci Mar 14 '14 at 13:57
  • @COBOL I'm talking about the actual implementation of the `Get` method on your concrete `ICrudService` – Kenneth Mar 14 '14 at 14:00
  • No I did not write the `And` method - it is meant to add one lambda expression to another – COBOL Mar 14 '14 at 14:00
  • The `And` method probably comes from the `LinqKit` referenced at the top. – MEMark Mar 14 '14 at 14:16

3 Answers3

24

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
  • 202,030
  • 26
  • 332
  • 449
  • How can i change this, but still achieve the same thing? – COBOL Mar 14 '14 at 14:05
  • @COBOL As I say in my answer, you need to call `AsExpandable` on all of your `IQueryable` objects before using any LinqKit methods. – Servy Mar 14 '14 at 14:06
  • Sorry, can you please show me how to call `AsExpandable` on the IQueryable object? I'm very confused – COBOL Mar 14 '14 at 14:07
5

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);
    }
}
Jon Ediger
  • 949
  • 1
  • 10
  • 27
2

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);
}
Aducci
  • 26,101
  • 8
  • 63
  • 67