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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18976495/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 13:46:01  来源:igfitidea点击:

LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interface

c#.netentity-frameworkexpression-treesdbcontext

提问by Steven

I have the following generic extension method:

我有以下通用扩展方法:

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

Unfortunately Entity Framework doesn't know how to handle the predicatesince C# converted the predicate to the following:

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

e => ((IEntity)e).Id == id

Entity Framework throws the following exception:

实体框架抛出以下异常:

Unable to cast the type 'IEntity' to type 'SomeEntity'. LINQ to Entities only supports casting EDM primitive or enumeration types.

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

How can we make Entity Framework work with our IEntityinterface?

我们如何让实体框架与我们的IEntity界面一起工作?

采纳答案by Sam

I was able to resolve this by adding the classgeneric type constraint to the extension method. I'm not sure why it works, though.

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

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

回答by Steven

Entity Framework doesn't support this out of the box, but an ExpressionVisitorthat translates the expression is easily written:

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

The only thing you'll have to to is to convert the passed in predicate using the expression visitor as follows:

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

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

    ...
}

Another -less flexible- approach is to make use of DbSet<T>.Find:

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

    ...
}

回答by Tadej Mali

Some additional explanations regarding the class"fix".

关于class“修复”的一些附加解释。

This answershows two different expressions, one with and the other without where T: classconstraint. Without the classconstraint we have:

这个答案显示了两种不同的表达方式,一种是有where T: class约束的,另一种是没有约束的。没有class约束,我们有:

e => e.Id == id // becomes: Convert(e).Id == id

and with the constraint:

并带有约束:

e => e.Id == id // becomes: e.Id == id

These two expressions are treated differently by the entity framework. Looking at the EF 6 sources, one can find that the exception comes from here, see ValidateAndAdjustCastTypes().

这两个表达式被实体框架区别对待。查看EF 6 来源,可以发现异常来自此处,请参阅ValidateAndAdjustCastTypes()

What happens is, that EF tries to cast IEntityinto something that makes sense the domain model world, however it fails in doing so, hence the exception is thrown.

发生的情况是,EF 试图转换IEntity为对域模型世界有意义的东西,但是它这样做失败了,因此抛出了异常。

The expression with the classconstraint does not contain the Convert()operator, cast is not tried and everything is fine.

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

It still remain open question, why LINQ builds different expressions? I hope that some C# wizard will be able to explain this.

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

回答by Justin

I had the same error but a similar but different problem. I was trying to create an extension function that returned IQueryable but the filter criteria was based on the base class.

我有同样的错误,但有类似但不同的问题。我试图创建一个返回 IQueryable 的扩展函数,但过滤条件基于基类。

i eventually found the solution which was for my extension method to call .Select(e => e as T) where T is the child class and e is the base class.

我最终找到了让我的扩展方法调用 .Select(e => e as T) 的解决方案,其中 T 是子类,e 是基类。

full details are here: Create IQueryable<T> extension using base class in EF

完整详细信息在这里: 使用 EF 中的基类创建 IQueryable<T> 扩展