LINQ to Entities solo admite la conversión de primitiva EDM o tipos de enumeración con la interfaz IEntity

Tengo el siguiente método de extensión genérico:

public static T GetById(this IQueryable collection, Guid id) where T : IEntity { Expression<Func> 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; } 

Desafortunadamente, Entity Framework no sabe cómo manejar el predicate ya que C # convirtió el predicado a lo siguiente:

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

Entity Framework arroja la siguiente excepción:

No se puede lanzar el tipo ‘IEntity’ para escribir ‘SomeEntity’. LINQ to Entities solo admite la conversión de primitiva EDM o tipos de enumeración.

¿Cómo podemos hacer que Entity Framework funcione con nuestra interfaz IEntity ?

Pude resolver esto agregando la restricción de tipo genérico de clase al método de extensión. Aunque no estoy seguro de por qué funciona.

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

Algunas explicaciones adicionales con respecto a la class “corregir”.

Esta respuesta muestra dos expresiones diferentes, una con y la otra sin where T: class restricción de where T: class . Sin la restricción de class tenemos:

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

y con la restricción:

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

Estas dos expresiones son tratadas de manera diferente por el marco de la entidad. En cuanto a las fonts de EF 6 , uno puede encontrar que la excepción proviene de aquí, vea ValidateAndAdjustCastTypes() .

Lo que sucede es que EF intenta convertir IEntity en algo que tiene sentido en el mundo del modelo de dominio, pero falla al hacerlo, por lo tanto, se lanza la excepción.

La expresión con la restricción de class no contiene el operador Convert() , el molde no se prueba y todo está bien.

Sigue siendo una pregunta abierta, ¿por qué LINQ construye diferentes expresiones? Espero que algún asistente de C # pueda explicar esto.

Entity Framework no es compatible con esto de manera predeterminada, pero un ExpressionVisitor que traduce la expresión se escribe fácilmente:

 private sealed class EntityCastRemoverVisitor : ExpressionVisitor { public static Expression> Convert( Expression> predicate) { var visitor = new EntityCastRemoverVisitor(); var visitedExpression = visitor.Visit(predicate); return (Expression>)visitedExpression; } protected override Expression VisitUnary(UnaryExpression node) { if (node.NodeType == ExpressionType.Convert && node.Type == typeof(IEntity)) { return node.Operand; } return base.VisitUnary(node); } } 

Lo único que deberá hacer es convertir el predicado pasado con la expresión visitante de la siguiente manera:

 public static T GetById(this IQueryable collection, Expression> predicate, Guid id) where T : IEntity { T entity; // Add this line! predicate = EntityCastRemoverVisitor.Convert(predicate); try { entity = collection.SingleOrDefault(predicate); } ... } 

Otro enfoque, menos flexible, es hacer uso de DbSet.Find :

 // NOTE: This is an extension method on DbSet instead of IQueryable public static T GetById(this DbSet collection, Guid id) where T : class, IEntity { T entity; // Allow reporting more descriptive error messages. try { entity = collection.Find(id); } ... }