LINQ to Entities prend en charge uniquement les types de primitive ou d’énumération EDM avec interface IEntity

J’ai la méthode d’extension générique suivante:

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(ssortingng.Format( "There was an error resortingeving an {0} with id {1}. {2}", typeof(T).Name, id, ex.Message), ex); } if (entity == null) { throw new KeyNotFoundException(ssortingng.Format( "{0} with id {1} was not found.", typeof(T).Name, id)); } return entity; } 

Malheureusement, Entity Framework ne sait pas comment gérer le predicate car C # a converti le prédicat suivant:

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

Entity Framework lève l’exception suivante:

Impossible de convertir le type ‘IEntity’ pour taper ‘SomeEntity’. LINQ to Entities ne prend en charge que les types de primitive ou d’énumération EDM.

Comment pouvons-nous faire fonctionner Entity Framework avec notre interface IEntity ?

    J’ai pu résoudre ce problème en ajoutant la contrainte de type générique de class à la méthode d’extension. Je ne sais pas pourquoi cela fonctionne, cependant.

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

    Quelques explications supplémentaires concernant la class “fix”.

    Cette réponse montre deux expressions différentes, l’une avec et l’autre sans where T: class contrainte de where T: class . Sans la contrainte de class , nous avons:

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

    et avec la contrainte:

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

    Ces deux expressions sont traitées différemment par le framework d’entités. En regardant les sources EF 6 , on peut trouver que l’exception vient d’ ici, voir ValidateAndAdjustCastTypes() .

    Ce qui se passe, c’est que EF essaie de IEntity en quelque chose qui fait sens dans le monde du modèle de domaine, mais cela échoue, donc l’exception est levée.

    L’expression avec la contrainte de class ne contient pas l’opérateur Convert() , cast n’est pas essayé et tout va bien.

    Il rest toujours une question ouverte, pourquoi LINQ construit des expressions différentes? J’espère qu’un assistant C # pourra l’expliquer.

    Entity Framework ne prend pas cela en charge, mais un ExpressionVisitor qui traduit l’expression est facile à écrire:

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

    La seule chose à faire est de convertir le prédicat passé en utilisant le visiteur d’expression comme suit:

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

    Une autre approche flexible est d’utiliser 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); } ... }