LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interface

33,760

Solution 1

I was able to resolve this by adding the class generic type constraint to the extension method. I'm not sure why it works, though.

public static T GetById<T>(this IQueryable<T> collection, Guid id)
    where T : class, IEntity
{
    //...
}

Solution 2

Some additional explanations regarding the class "fix".

This answer shows two different expressions, one with and the other without where T: class constraint. Without the class constraint we have:

e => e.Id == id // becomes: Convert(e).Id == id

and with the constraint:

e => e.Id == id // becomes: e.Id == id

These two expressions are treated differently by the entity framework. Looking at the EF 6 sources, one can find that the exception comes from here, see ValidateAndAdjustCastTypes().

What happens is, that EF tries to cast IEntity into something that makes sense the domain model world, however it fails in doing so, hence the exception is thrown.

The expression with the class constraint does not contain the Convert() operator, cast is not tried and everything is fine.

It still remain open question, why LINQ builds different expressions? I hope that some C# wizard will be able to explain this.

Solution 3

Entity Framework doesn't support this out of the box, but an ExpressionVisitor that translates the expression is easily written:

private sealed class EntityCastRemoverVisitor : ExpressionVisitor
{
    public static Expression<Func<T, bool>> Convert<T>(
        Expression<Func<T, bool>> predicate)
    {
        var visitor = new EntityCastRemoverVisitor();

        var visitedExpression = visitor.Visit(predicate);

        return (Expression<Func<T, bool>>)visitedExpression;
    }

    protected override Expression VisitUnary(UnaryExpression node)
    {
        if (node.NodeType == ExpressionType.Convert && node.Type == typeof(IEntity))
        {
            return node.Operand;
        }

        return base.VisitUnary(node);
    }
}

The only thing you'll have to to is to convert the passed in predicate using the expression visitor as follows:

public static T GetById<T>(this IQueryable<T> collection, 
    Expression<Func<T, bool>> predicate, Guid id)
    where T : IEntity
{
    T entity;

    // Add this line!
    predicate = EntityCastRemoverVisitor.Convert(predicate);

    try
    {
        entity = collection.SingleOrDefault(predicate);
    }

    ...
}

Another -less flexible- approach is to make use of DbSet<T>.Find:

// NOTE: This is an extension method on DbSet<T> instead of IQueryable<T>
public static T GetById<T>(this DbSet<T> collection, Guid id) 
    where T : class, IEntity
{
    T entity;

    // Allow reporting more descriptive error messages.
    try
    {
        entity = collection.Find(id);
    }

    ...
}

Solution 4

I had the same error but a similar but different problem. I was trying to create an extension function that returned IQueryable but the filter criteria was based on the base class.

i eventually found the solution which was for my extension method to call .Select(e => e as T) where T is the child class and e is the base class.

full details are here: Create IQueryable<T> extension using base class in EF

Share:
33,760

Related videos on Youtube

Steven
Author by

Steven

I'm a freelance developer and architect from The Netherlands, working with .NET technology on a daily basis. You can read my weblog or follow me on Twitter. I'm a developer of the Simple Injector open source dependency injection library and coauthor of the book Dependency Injection Principles, Practices, and Patterns.

Updated on December 11, 2020

Comments

  • Steven
    Steven over 3 years

    I have the following generic extension method:

    public static T GetById<T>(this IQueryable<T> collection, Guid id) 
        where T : IEntity
    {
        Expression<Func<T, bool>> predicate = e => e.Id == id;
    
        T entity;
    
        // Allow reporting more descriptive error messages.
        try
        {
            entity = collection.SingleOrDefault(predicate);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(string.Format(
                "There was an error retrieving an {0} with id {1}. {2}",
                typeof(T).Name, id, ex.Message), ex);
        }
    
        if (entity == null)
        {
            throw new KeyNotFoundException(string.Format(
                "{0} with id {1} was not found.",
                typeof(T).Name, id));
        }
    
        return entity;
    }
    

    Unfortunately Entity Framework doesn't know how to handle the predicate since C# converted the predicate to the following:

    e => ((IEntity)e).Id == id
    

    Entity Framework throws the following exception:

    Unable to cast the type 'IEntity' to type 'SomeEntity'. LINQ to Entities only supports casting EDM primitive or enumeration types.

    How can we make Entity Framework work with our IEntity interface?

  • berko
    berko over 10 years
    Works for me too! I would love for someone to be able to explain this. #linqblackmagic
  • Jace Rhea
    Jace Rhea about 9 years
    Thanks for the explanation.
  • yrahman
    yrahman about 9 years
    Can you please explain how did you add this constraint
  • jwize
    jwize over 8 years
    My guess is that the class type is used rather than the interface type. EF doesn't know about the interface type so it can't convert it to SQL. With the class constraint the type inferred is the DbSet<T> type which EF knows what to do with.
  • Nick N.
    Nick N. over 8 years
    @JonSkeet someone tried to summon a C# wizard here. Where are you?
  • Anders
    Anders over 7 years
    Perfect, it's great being able to perform Interface-based queries and still maintain the collection as IQueryable. A bit annoying however that there is basically no way of thinking up this fix, without knowing the inner workings of EF.
  • War
    War about 5 years
    What you're seeing here is a compiler time constraint which allows the C# compiler to determine that T is of type IEntity within the method so is able to determine that any usage of IEntity "stuff" is valid as during compile time the MSIL code generated will auto perform this check for you prior to the call. To clarify, adding "class" as a type constraint here allows collection.FirstOrDefault() to run correctly as it likely returns a new instance of T calling a default ctor on a class based type.