c# - example - LINQ to Entities solo admite la conversión de primitiva EDM o tipos de enumeración con la interfaz IEntity
linq select (3)
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 fuentes 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.
Tengo el siguiente método de extensión genérico:
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;
}
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
?
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<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);
}
}
Lo único que deberá hacer es convertir el predicado pasado con la expresión visitante de la siguiente manera:
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);
}
...
}
Otro enfoque, menos flexible, es hacer uso de 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);
}
...
}
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<T>(this IQueryable<T> collection, Guid id)
where T : class, IEntity
{
//...
}