LINQ to Entities仅支持使用IEntity接口转换EDM原语或枚举类型

Ste*_*ven 89 .net c# entity-framework expression-trees dbcontext

我有以下通用扩展方法:

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;
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,实体框架不知道如何处理predicate自从C#将谓词转换为以下内容:

e => ((IEntity)e).Id == id
Run Code Online (Sandbox Code Playgroud)

实体框架抛出以下异常:

无法将"IEntity"类型转换为"SomeEntity"类型.LINQ to Entities仅支持转换EDM原语或枚举类型.

我们如何使用我们的IEntity界面使Entity Framework工作?

Sam*_*Sam 180

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

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

  • 也适合我!我希望有人能够解释这一点.#linqblackmagic (6认同)
  • 我的猜测是使用类类型而不是接口类型.EF不知道接口类型,因此无法将其转换为SQL.对于类约束,推断的类型是DbSet <T>类型,EF知道如何处理. (5认同)
  • 完美,能够执行基于接口的查询并且仍然将集合维护为 IQueryable 真是太棒了。然而有点烦人的是,在不了解 EF 的内部工作原理的情况下,基本上没有办法想出这个修复程序。 (2认同)

Tad*_*ali 63

关于class"修复"的一些额外解释.

这个答案显示了两个不同的表达式,一个用于,另一个没有where T: class约束.没有class约束我们有:

e => e.Id == id // becomes: Convert(e).Id == id
Run Code Online (Sandbox Code Playgroud)

并有约束:

e => e.Id == id // becomes: e.Id == id
Run Code Online (Sandbox Code Playgroud)

实体框架对这两个表达式的处理方式不同.查看EF 6源代码,可以发现异常来自此处,请参阅ValidateAndAdjustCastTypes().

会发生什么,EF试图投入IEntity到有意义的领域模型世界,但是它没有这样做,因此引发了异常.

class约束的表达式不包含Convert()运算符,不尝试强制转换,一切都很好.

它仍然是一个悬而未决的问题,为什么LINQ构建不同的表达 我希望有些C#向导能够解释这一点.

  • @JonSkeet有人试图在这里召唤一个C#向导.你在哪? (9认同)

Ste*_*ven 22

实体框架不支持开箱即用,但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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您唯一要做的就是使用表达式visitor转换传入的谓词,如下所示:

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);
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

另一种灵活方法是利用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);
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)