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接口配合使用?

LINQ to Entities 仅支持使用 IEntity 接口强制转换 EDM 基元或枚举类型

我能够通过将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 扩展