【问题标题】:LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interfaceLINQ to Entities 仅支持使用 IEntity 接口强制转换 EDM 基元或枚举类型
【发布时间】: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 接口一起工作?

【问题讨论】:

    标签: c# .net entity-framework expression-trees dbcontext


    【解决方案1】:

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

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

    【讨论】:

    • 也适合我!我希望有人能够解释这一点。 #linqblackmagic
    • 你能解释一下你是如何添加这个约束的
    • 我的猜测是使用了类类型而不是接口类型。 EF 不知道接口类型,因此无法将其转换为 SQL。使用类约束,推断的类型是 DbSet 类型,EF 知道如何处理。
    • 完美,能够执行基于接口的查询并仍将集合维护为 IQueryable 非常棒。有点烦人,但是在不了解 EF 的内部工作原理的情况下,基本上没有办法解决这个问题。
    • 您在此处看到的是编译器时间约束,它允许 C# 编译器确定 T 是方法中的 IEntity 类型,因此能够确定 IEntity “stuff”的任何使用都是有效的因为在编译期间,生成的 MSIL 代码将在调用之前自动为您执行此检查。澄清一下,在此处添加“类”作为类型约束允许 collection.FirstOrDefault() 正确运行,因为它可能会返回一个新的 T 实例,该实例调用基于类的类型的默认 ctor。
    【解决方案2】:

    关于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&lt;T&gt;.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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-13
        • 1970-01-01
        • 1970-01-01
        • 2011-03-14
        • 1970-01-01
        相关资源
        最近更新 更多