wake-up-neo.com

LINQ to Entities unterstützt nur die Übertragung von EDM-Grundelementen oder Aufzählungstypen mit IEntity-Schnittstelle

Ich habe die folgende generische Erweiterungsmethode:

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

Leider kann das Entity Framework nicht mit der predicate umgehen, da C # das Prädikat in Folgendes konvertiert hat:

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

Entity Framework löst die folgende Ausnahme aus:

Der Typ 'IEntity' kann nicht in 'SomeEntity' umgewandelt werden. LINQ zu Entities unterstützt nur Casting-EDM-Primitiv- oder Aufzählungstypen.

Wie kann das Entity Framework mit unserer IEntity-Schnittstelle funktionieren?

81
Steven

Ich konnte dieses Problem beheben, indem Sie die generische Typeinschränkung class zur Erweiterungsmethode hinzufügen. Ich bin nicht sicher, warum es funktioniert.

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

Einige zusätzliche Erklärungen zum "fix" von class.

Diese Antwort zeigt zwei verschiedene Ausdrücke, einen mit und den anderen ohne where T: class-Einschränkung. Ohne die class-Einschränkung haben wir:

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

und mit der Einschränkung:

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

Diese beiden Ausdrücke werden vom Entity-Framework unterschiedlich behandelt. Betrachtet man die EF 6-Quellen , so kann man feststellen, dass die Ausnahme von hier kommt, siehe ValidateAndAdjustCastTypes() .

Was passiert ist, dass EF versucht, IEntity in etwas umzuwandeln, das die Domänenmodellwelt als sinnvoll erweist, jedoch scheitert es daran, daher wird die Ausnahme ausgelöst.

Der Ausdruck mit der class-Einschränkung enthält nicht den Convert()-Operator. Die Umwandlung wird nicht versucht und alles ist in Ordnung.

Es bleibt immer noch eine offene Frage, warum LINQ unterschiedliche Ausdrücke erstellt. Ich hoffe, dass einige C # -Operatoren das erklären können.

59
Tadej Mali

Entity Framework unterstützt dies nicht sofort, aber eine ExpressionVisitor, die den Ausdruck übersetzt, lässt sich leicht schreiben:

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

Das einzige, was Sie tun müssen, ist, das übergebene Prädikat mit dem Ausdruck besucher wie folgt zu konvertieren:

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

    ...
}

Ein weiterer flexibler Ansatz ist die Verwendung von 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

Ich hatte den gleichen Fehler, aber ein ähnliches, aber anderes Problem. Ich habe versucht, eine Erweiterungsfunktion zu erstellen, die IQueryable zurückgab, aber die Filterkriterien basierten auf der Basisklasse.

ich fand schließlich die Lösung, die meine Erweiterungsmethode war. Select (e => e als T), wobei T die Kindklasse und E die Basisklasse ist.

die vollständigen Details finden Sie hier: Erstellen Sie die Erweiterung IQueryable <T> mithilfe der Basisklasse in EF .

0
Justin