108 votes

LINQ to Entities ne prend en charge que la conversion des types primitifs ou énumérés EDM avec l'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> predicate = e => e.Id == id;

    T entity;

    // Permettre de rapporter des messages d'erreur plus descriptifs.
    try
    {
        entity = collection.SingleOrDefault(predicate);
    }
    catch (Exception ex)
    {
        throw new InvalidOperationException(string.Format(
            "Une erreur s'est produite lors de la récupération d'un {0} avec l'identifiant {1}. {2}",
            typeof(T).Name, id, ex.Message), ex);
    }

    if (entity == null)
    {
        throw new KeyNotFoundException(string.Format(
            "{0} avec l'identifiant {1} n'a pas été trouvé.",
            typeof(T).Name, id));
    }

    return entity;
}

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

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

Entity Framework lance l'exception suivante :

Impossible de convertir le type 'IEntity' en type 'SomeEntity'. LINQ to Entities ne prend en charge que la conversion de types EDM primitifs ou énumérés.

Comment pouvons-nous faire en sorte que Entity Framework fonctionne avec notre interface IEntity ?

213voto

Sam Points 3542

J'ai pu résoudre cela en ajoutant la contrainte de type générique class à la méthode d'extension. Je ne suis pas sûr pourquoi ça fonctionne, cependant.

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

70voto

Tadej Mali Points 150

Quelques explications supplémentaires concernant la classe fix.

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

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

et avec la contrainte :

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

Ces deux expressions sont traitées différemment par l'Entity Framework. En regardant les sources d'EF 6, on peut trouver que l'exception provient de ici, voir ValidateAndAdjustCastTypes().

La tentative d'EF est de caster IEntity en quelque chose qui a du sens dans le monde du modèle de domaine, cependant cela échoue, d'où l'exception est déclenchée.

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

La question reste ouverte, pourquoi LINQ construit-il des expressions différentes? J'espère qu'un mage du C# sera capable d'expliquer cela.

24voto

Steven Points 56939

Entity Framework ne prend pas en charge cela par défaut, mais un ExpressionVisitor qui traduit l'expression est facilement écrit:

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 que vous aurez à faire est de convertir le prédicat passé en utilisant l'expression visitor comme suit:

public static T GetById(this IQueryable collection, 
    Expression> prédicat, Guid id)
    where T : IEntity
{
    T entity;

    // Ajouter cette ligne!
    prédicat = EntityCastRemoverVisitor.Convert(prédicat);

    try
    {
        entity = collection.SingleOrDefault(prédicat);
    }

    ...
}

Une autre approche -moins flexible- est d'utiliser DbSet.Find:

// REMARQUE : Il s'agit d'une méthode d'extension sur DbSet au lieu de IQueryable
public static T GetById(this DbSet collection, Guid id) 
    where T : class, IEntity
{
    T entity;

    // Permettre de signaler des messages d'erreur plus descriptifs.
    try
    {
        entity = collection.Find(id);
    }

    ...
}

1voto

Justin Points 171

J'ai rencontré la même erreur mais un problème similaire mais différent. J'essayais de créer une fonction d'extension retournant IQueryable mais le critère de filtre était basé sur la classe de base.

J'ai finalement trouvé la solution qui consistait à ce que ma méthode d'extension appelle .Select(e => e as T) où T est la classe enfant et e est la classe de base.

vous trouverez tous les détails ici: Create IQueryable extension using base class in EF

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X