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 Сущности поддерживают только приведение примитивов EDM или типов перечисления.
как мы можем сделать сущность Основы работы с 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
эти два выражения по-разному обрабатываются Entity framework. Глядя на 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); } }
единственное, что вам нужно будет сделать, это преобразовать переданный предикат, используя выражение 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); } ... }
другое -менее гибкий подход заключается в использовании
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); } ... }