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