LINQtoEntity 只支持使用 IEntity 接口强制转换 EDM 基元或枚举类型

我有以下通用扩展方法:

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;
}

遗憾的是,实体框架不知道如何处理 predicate,因为 C # 将谓词转换为以下内容:

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

实体框架引发以下异常:

无法将类型“ IEntity”强制转换为类型“ some Entity” 实体只支持强制转换 EDM 基元或枚举类型。

我们如何使实体框架与我们的 IEntity接口工作?

34699 次浏览

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);
}


...
}

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
{
//...
}

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.

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