LINQ to Entities仅支持使用IEntity接口来投射EDM原语或枚举types

我有以下通用的扩展方法:

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

不幸的是,entity framework不知道如何处理predicate因为C#将谓词转换为以下内容:

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

entity framework引发以下exception:

无法强制types“IEntity”键入“SomeEntity”。 LINQ to Entities只支持投射EDM原始types或枚举types。

我们如何使我们的IEntity接口的entity framework工作?

我能够通过将classgenericstypes约束添加到扩展方法来解决此问题。 不过,我不确定它为什么起作用。

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

关于class “修复”的一些额外的解释。

这个答案显示了两个不同的expression式,一个和另一个没有where T: class约束。 没有class约束,我们有:

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

并与约束:

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

这两个expression式被entity framework区别对待。 查看EF 6源代码 ,可以发现exception来自这里,请参阅ValidateAndAdjustCastTypes()

会发生什么呢,EF试图把IEntity转换成有意义的领域模型世界,然而它却失败了,因此抛出了exception。

class约束的expression式不包含Convert()运算符,不会尝试投射并且一切正常。

它仍然是一个悬而未决的问题,为什么LINQ构build不同的expression式? 我希望有一些C#向导能够解释这一点。

entity framework不支持这个开箱即用,但可以很容易地编写一个ExpressionVisitor来翻译expression式:

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

您唯一需要做的就是使用expression式访问器转换传入的谓词,如下所示:

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

另一个灵活的方法是利用DbSet<T>.FindDbSet<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); } ... }