LINQ to Entities supporta solo il casting di tipi primitivi o di enumerazione EDM con l'interfaccia IEntity


96

Ho il seguente metodo di estensione generico:

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

Sfortunatamente Entity Framework non sa come gestire predicatepoiché C # ha convertito il predicato nel seguente:

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

Entity Framework genera la seguente eccezione:

Impossibile eseguire il cast del tipo "IEntity" per digitare "SomeEntity". LINQ to Entities supporta solo il casting di tipi primitivi o di enumerazione EDM.

Come possiamo far funzionare Entity Framework con la nostra IEntityinterfaccia?

Risposte:


188

Sono stato in grado di risolvere questo problema aggiungendo il classvincolo di tipo generico al metodo di estensione. Non sono sicuro del perché funzioni, però.

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

6
Funziona anche per me! Mi piacerebbe che qualcuno fosse in grado di spiegarlo. #linqblackmagic
berko

Puoi per favore spiegare come hai aggiunto questo vincolo
yrahman

5
La mia ipotesi è che venga utilizzato il tipo di classe anziché il tipo di interfaccia. EF non conosce il tipo di interfaccia, quindi non può convertirlo in SQL. Con il vincolo di classe il tipo dedotto è il tipo DbSet <T> con cui EF sa cosa fare.
jwize

1
Perfetto, è fantastico poter eseguire query basate sull'interfaccia e mantenere la raccolta come IQueryable. Un po 'fastidioso, tuttavia, che non ci sia praticamente modo di pensare a questa soluzione, senza conoscere il funzionamento interno di EF.
Anders

Quello che stai vedendo qui è un vincolo di tempo del compilatore che consente al compilatore C # di determinare che T è di tipo IEntity all'interno del metodo quindi è in grado di determinare che qualsiasi utilizzo di "roba" IEntity è valido poiché durante la compilazione il codice MSIL generato eseguirà automaticamente questo controllo prima della chiamata. Per chiarire, l'aggiunta di "classe" come vincolo di tipo qui consente a collection.FirstOrDefault () di funzionare correttamente poiché probabilmente restituisce una nuova istanza di T che chiama un ctor predefinito su un tipo basato sulla classe.
Guerra

64

Alcune spiegazioni aggiuntive riguardo la class"correzione".

Questa risposta mostra due diverse espressioni, una con e l'altra senza where T: classvincolo. Senza classvincolo abbiamo:

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

e con il vincolo:

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

Queste due espressioni vengono trattate in modo diverso dal framework dell'entità. Guardando le fonti EF 6 , si può scoprire che l'eccezione viene da qui, vediValidateAndAdjustCastTypes() .

Quello che succede è che EF cerca di eseguire il cast IEntityin qualcosa che abbia senso nel mondo del modello di dominio, tuttavia non riesce a farlo, quindi viene generata l'eccezione.

L'espressione con il classvincolo non contiene l' Convert()operatore, il cast non viene provato e tutto va bene.

Rimane ancora una domanda aperta, perché LINQ crea espressioni diverse? Spero che qualche procedura guidata di C # sia in grado di spiegarlo.


1
Grazie per la spiegazione.
Jace Rhea

9
@JonSkeet qualcuno ha cercato di convocare una procedura guidata C # qui. Dove sei?
Nick N.

23

Entity Framework non lo supporta immediatamente, ma ExpressionVisitorè facile scrivere un'espressione che traduce:

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

L'unica cosa che dovrai fare è convertire il predicato passato usando l'espressione visitatore come segue:

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

    ...
}

Un altro approccio, meno flessibile, consiste nell'utilizzare 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);
    }

    ...
}

1

Ho avuto lo stesso errore ma un problema simile ma diverso. Stavo cercando di creare una funzione di estensione che restituisse IQueryable ma i criteri di filtro erano basati sulla classe base.

Alla fine ho trovato la soluzione che era per il mio metodo di estensione chiamare .Select (e => e come T) dove T è la classe figlio ed e è la classe base.

i dettagli completi sono qui: Crea l'estensione IQueryable <T> usando la classe base in EF

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.