LINQ to Entities 仅支持使用 IEntity 接口强制转换 EDM 基元或枚举类型
本文关键字:EDM 转换 类型 枚举 接口 Entities to 支持 IEntity LINQ | 更新日期: 2023-09-27 18:32:36
我有以下通用扩展方法:
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"转换为类型"某个实体"。LINQ to 实体仅支持强制转换 EDM 基元或枚举类型。
如何使实体框架与我们的IEntity
接口配合使用?
我能够通过将class
泛型类型约束添加到扩展方法来解决此问题。不过,我不确定它为什么有效。
public static T GetById<T>(this IQueryable<T> collection, Guid id)
where T : class, IEntity
{
//...
}
关于class
"修复"的一些附加说明。
此答案显示了两种不同的表达式,一种有约束,另一种没有约束where T: class
。如果没有class
约束,我们有:
e => e.Id == id // becomes: Convert(e).Id == id
并带有约束:
e => e.Id == id // becomes: e.Id == id
实体框架对这两个表达式的处理方式不同。查看EF 6源,可以发现异常来自此处,请参阅ValidateAndAdjustCastTypes()
。
发生的情况是,EF 尝试将IEntity
转换为对域模型世界有意义的内容,但它未能这样做,因此会引发异常。
具有 class
约束的表达式不包含 Convert()
运算符,未尝试强制转换,一切正常。
为什么 LINQ 会生成不同的表达式,这仍然是一个悬而未决的问题?我希望一些 C# 向导能够解释这一点。
框架不支持开箱即用,但翻译表达式的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);
}
}
您唯一需要做的就是使用表达式 visitor 转换传递的 in 谓词,如下所示:
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);
}
...
}
我遇到了相同的错误,但有一个相似但不同的问题。我试图创建一个返回IQueryable的扩展函数,但过滤条件基于基类。
我最终找到了让我的扩展方法调用的解决方案。选择(e => e 作为 T),其中 T 是子类,e 是基类。
完整的细节在这里:在 EF 中使用基类创建 IQueryable