LINQ to Entities 仅支持使用 IEntity 接口强制转换 EDM 基元或枚举类型
Posted
技术标签:
【中文标题】LINQ to Entities 仅支持使用 IEntity 接口强制转换 EDM 基元或枚举类型【英文标题】:LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interface 【发布时间】:2013-09-29 08:48:52 【问题描述】:我有以下通用扩展方法:
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”转换为类型“SomeEntity”。 LINQ 到 实体仅支持转换 EDM 基元或枚举类型。
我们如何使实体框架与我们的IEntity
接口一起工作?
【问题讨论】:
【参考方案1】:我能够通过将class
泛型类型约束添加到扩展方法来解决此问题。不过,我不确定它为什么会起作用。
public static T GetById<T>(this IQueryable<T> collection, Guid id)
where T : class, IEntity
//...
【讨论】:
也适合我!我希望有人能够解释这一点。 #linqblackmagic 你能解释一下你是如何添加这个约束的 我的猜测是使用了类类型而不是接口类型。 EF 不知道接口类型,因此无法将其转换为 SQL。使用类约束,推断的类型是 DbSet关于class
“修复”的一些补充说明。
This answer 显示两个不同的表达式,一个有where T: class
约束,另一个没有where T: class
约束。如果没有 class
约束,我们有:
e => e.Id == id // becomes: Convert(e).Id == id
并且有约束:
e => e.Id == id // becomes: e.Id == id
这两个表达式被实体框架区别对待。查看EF 6 sources,可以发现异常来自here, see ValidateAndAdjustCastTypes()
。
发生的情况是,EF 尝试将 IEntity
强制转换为对域模型世界有意义的东西,但是这样做失败了,因此引发了异常。
具有class
约束的表达式不包含Convert()
运算符,未尝试强制转换,一切正常。
这仍然是一个悬而未决的问题,为什么 LINQ 构建不同的表达式?我希望某个 C# 向导能够解释这一点。
【讨论】:
感谢您的解释。 @JonSkeet 有人试图在这里召唤一个 C# 向导。你在哪里?【参考方案3】:实体框架不支持这个开箱即用,但可以很容易地编写一个转换表达式的ExpressionVisitor
:
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);
您唯一需要做的就是使用以下表达式访问者转换传入的谓词:
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>.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);
...
【讨论】:
【参考方案4】:我有同样的错误,但有类似但不同的问题。我试图创建一个返回 IQueryable 但过滤条件基于基类的扩展函数。
我最终找到了让我的扩展方法调用 .Select(e => e as T) 的解决方案,其中 T 是子类,e 是基类。
完整的细节在这里: Create IQueryable<T> extension using base class in EF
【讨论】:
以上是关于LINQ to Entities 仅支持使用 IEntity 接口强制转换 EDM 基元或枚举类型的主要内容,如果未能解决你的问题,请参考以下文章
无法将类型A强制转换为类型B. LINQ to Entities仅支持强制转换EDM原语或枚举类型
分页错误:仅 LINQ to Entities 中的排序输入支持“跳过”方法。方法 'OrderBy' 必须在方法 'Skip' 之前调用
无法将类型“System.Nullable`1”强制转换为类型“System.Object”。LINQ to Entities 仅支持强制转换 EDM 基元或枚举类型。
LINQ to Entities 不支持 LINQ 表达式节点类型“Invoke”