web-dev-qa-db-fra.com

LINQ to Entities ne prend en charge que le transtypage de types de primitive ou d'énumération EDM avec l'interface IEntity

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

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

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

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

Entity Framework lève l'exception suivante:

Impossible de convertir le type 'IEntity' en 'SomeEntity'. LINQ à Les entités ne prennent en charge que la diffusion des types de primitive ou d’énumération EDM.

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

81
Steven

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

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

Quelques explications supplémentaires concernant la "variable" class.

Cette réponse montre deux expressions différentes, l'une avec et l'autre sans contrainte where T: class. Sans la contrainte 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 cadre de l'entité. En regardant les sources EF 6 , on peut constater que l’exception provient de ici, voir ValidateAndAdjustCastTypes() .

Ce qui se passe, c’est que EF essaie de transformer IEntity en un élément qui a du sens pour le monde des modèles de domaine, même s’il échoue, ce qui provoque une exception.

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

Il reste toujours question ouverte, pourquoi LINQ construit différentes expressions? J'espère qu'un assistant de C # pourra expliquer cela.

59
Tadej Mali

Entity Framework ne prend pas cela en charge immédiatement, mais une ExpressionVisitor qui traduit l'expression est facilement écrite

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

La seule chose à faire est de convertir le prédicat passé en utilisant l'expression visiteur telle que:

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

    ...
}

Une autre approche moins flexible consiste à utiliser 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);
    }

    ...
}
20
Steven

J'ai eu la même erreur mais un problème similaire mais différent. J'essayais de créer une fonction d'extension qui renvoyait IQueryable, mais les critères de filtrage étaient basés sur la classe de base.

j'ai finalement trouvé la solution qui consistait pour ma méthode d'extension à appeler .Select (e => e comme T) où T est la classe enfant et e est la classe de base.

tous les détails sont ici: Créer une extension IQueryable <T> en utilisant la classe de base dans EF

0
Justin