LINQ to Entities поддерживает приведение только типов примитивов и перечислений EDM с интерфейсом IEntity.

У меня есть следующий общий метод расширения:

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

К сожалению, Entity Framework не знает, как обращаться с predicate поскольку C# преобразовал предикат в следующее:

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

Entity Framework выдает следующее исключение:

Невозможно привести тип IEntity к типу SomeEntity. LINQ to Entities поддерживает только приведение типов примитивов и перечислений EDM.

Как мы можем заставить Entity Framework работать с нашими IEntity интерфейс?

3 ответа

Решение

Я смог решить эту проблему, добавив 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# смогут объяснить это.

Entity Framework не поддерживает это из коробки, но 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);
    }

    ...
}

У меня была та же ошибка, но похожая, но другая проблема. Я пытался создать функцию расширения, которая возвращала IQueryable, но критерии фильтра были основаны на базовом классе.

В конце концов я нашел решение, которое для моего метода расширения было вызвать.Select(e => e как T), где T - это дочерний класс, а e - это базовый класс.

полная информация здесь: создайте расширение IQueryable, используя базовый класс в EF

Другие вопросы по тегам