我有以下通用扩展方法:
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
Entity Framework抛出以下异常:
无法将“IEntity”类型转换为“SomeEntity”类型。 LINQ to 实体仅支持转换EDM原语或枚举类型。
我们如何使用IEntity
界面使用Entity Framework?
答案 0 :(得分:173)
我能够通过向扩展方法添加class
泛型类型约束来解决此问题。不过,我不确定它为什么会起作用。
public static T GetById<T>(this IQueryable<T> collection, Guid id)
where T : class, IEntity
{
//...
}
答案 1 :(得分:61)
关于class
“修复”的其他一些解释。
This answer显示两个不同的表达式,一个用于,另一个没有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#向导能够解释这一点。
答案 2 :(得分:21)
实体框架不支持开箱即用,但可以轻松编写转换表达式的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);
}
...
}
答案 3 :(得分:1)
我有相同的错误,但有相似但不同的问题。我试图创建一个扩展函数,该函数返回IQueryable,但过滤条件基于基类。
我最终找到了解决方法,该方法是我的扩展方法调用.Select(e => e作为T),其中T是子类,e是基类。
完整的详细信息在这里: Create IQueryable<T> extension using base class in EF