Wählen Sie eine Klausel aus, die Nicht-EF-Methodenaufrufe enthält

c# entity-framework linq linq-to-entities postgresql

Frage

Ich habe Probleme beim Erstellen einer Entity Framework LINQ-Abfrage, deren select-Klausel Methodenaufrufe für Nicht-EF-Objekte enthält.

Der folgende Code ist Teil einer App, mit der Daten aus einem DBMS in ein anderes Schema eines anderen DBMS umgewandelt werden. Im folgenden Code ist Role meine benutzerdefinierte Klasse, die nichts mit dem DBMS zu tun hat. Alle anderen Klassen werden von Entity Framework aus meinem DB-Schema generiert:

// set up ObjectContext's for Old and new DB schemas
var New = new NewModel.NewEntities();
var Old = new OldModel.OldEntities();

// cache all Role names and IDs in the new-schema roles table into a dictionary
var newRoles = New.roles.ToDictionary(row => row.rolename, row => row.roleid);

// create a list or Role objects where Name is name in the old DB, while
// ID is the ID corresponding to that name in the new DB
var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select new Role { Name = r.RoleName, ID = newRoles[r.RoleName] };
var list = roles.ToList();

Beim Aufrufen der ToList bekomme ich diese NotSupportedException:

LINQ to Entities erkennt die Methode 'Int32 get_Item (System.String)' nicht. Diese Methode kann nicht in einen Speicherausdruck übersetzt werden

Hört sich an, als ob LINQ-to-Entities meinen Anruf ablehnt, um den Wert aus dem Wörterbuch zu ziehen, wenn der Name als Schlüssel angegeben ist. Ich verstehe zwar nicht genug von EF, um zu wissen, warum dies ein Problem ist.

Ich verwende dotConnect von devart für den PostgreSQL- Entity-Framework-Anbieter, obwohl ich an dieser Stelle davon ausgehe , dass dies kein DBMS-spezifisches Problem ist.

Ich weiß, dass ich es schaffen kann, indem ich meine Abfrage in zwei Abfragen aufteilte:

var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r;
var roles2 = from r in roles.AsEnumerable()
            select new Role { Name = r.RoleName, ID = newRoles[r.RoleName] };
var list = roles2.ToList();

Ich habe mich jedoch gefragt, ob es einen eleganteren und effizienteren Weg gibt, dieses Problem zu lösen, im Idealfall ohne es in zwei Abfragen aufzuteilen.

Jedenfalls besteht meine Frage aus zwei Teilen:

Kann ich erstens diese LINQ-Abfrage in etwas umwandeln, das Entity Framework akzeptiert, im Idealfall ohne Aufteilung in zwei Teile?

Zweitens würde ich auch gerne ein wenig über EF verstehen, damit ich verstehen kann, warum EF meinen benutzerdefinierten .NET-Code nicht über den DB-Zugriff schichten kann. Mein DBMS hat keine Ahnung, wie eine Methode in einer Dictionary-Klasse aufgerufen werden soll. Warum kann EF diese Dictionary-Methodenaufrufe jedoch nicht einfach ausführen, nachdem bereits Daten aus der DB abgerufen wurden? Wenn ich mehrere EF-Abfragen zusammenstellen und benutzerdefinierten .NET-Code in die Mitte setzen möchte, würde ich davon ausgehen, dass dies fehlschlägt. In diesem Fall ist der .NET-Code jedoch nur am Ende. Warum ist dies ein Problem? EF? Ich gehe davon aus, dass die Antwort in etwa so aussieht: "Dieses Feature hat es nicht in EF 1.0 geschafft", aber ich suche ein wenig mehr Erklärung, warum dies schwer genug ist, um es aus EF 1.0 herauszulassen.

Akzeptierte Antwort

Das Problem ist, dass Sie bei der verzögerten Ausführung von Linq wirklich entscheiden müssen, wo die Verarbeitung erfolgen soll und welche Daten die Pipe an Ihre Clientanwendung übergeben sollen. In der ersten Instanz löst Linq den Ausdruck auf und ruft alle Rollendaten als Vorläufer ab

New.roles.ToDictionary(row => row.rolename, row => row.roleid);

Zu diesem Zeitpunkt werden die Daten von der Datenbank in den Client verschoben und in Ihr Wörterbuch umgewandelt. So weit, ist es gut.

Das Problem ist, dass Ihr zweiter Linq-Ausdruck Linq auffordert, die Transformation für die zweite DB mit dem Wörterbuch der DB auszuführen, um dies zu tun. Mit anderen Worten, es wird versucht, einen Weg zu finden, die gesamte Wörterbuchstruktur an die Datenbank zu übergeben, damit sie im Rahmen der verzögerten Ausführung der Abfrage den richtigen ID-Wert auswählen kann. Ich vermute, dass es gut klappt, wenn Sie die zweite Hälfte in geändert haben

var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r.RoleName;
var list = roles.ToDictionary(roleName => roleName, newRoles[roleName]);

Auf diese Weise wird Ihre Auswahl in der Datenbank aufgelöst (indem Sie nur den Mehrfachnamen auswählen), um den ToDictionary-Aufruf zu verarbeiten (was der Client erwartungsgemäß ausführen sollte). Dies ist im Wesentlichen genau das, was Sie in Ihrem zweiten Beispiel tun, da AsEnumerable die Daten zum Client zieht, bevor Sie sie im ToList-Aufruf verwenden. Sie könnten es genauso leicht in etwas ändern

var roles = from rl in Old.userrolelinks
            join r in Old.roles on rl.RoleID equals r.RoleID
            where rl.UserID == userId
            select r;
var list = roles.AsEnumerable().Select(r => new Role { Name = r.RoleName, ID = newRoles[r.RoleName] });

und es würde gleich klappen. Der Aufruf von AsEnumerable () löst die Abfrage auf, wobei die Daten zur Verwendung im darauf folgenden Select an den Client übertragen werden.

Beachten Sie, dass ich dies nicht getestet habe, aber soweit ich das Entity Framework verstehe, ist das meine beste Erklärung für das, was sich unter der Haube abspielt.


Beliebte Antwort

Jacob hat völlig recht. Sie können die gewünschte Abfrage nicht konvertieren, ohne sie in zwei Teile aufzuteilen, da Entity Framework den Aufruf get_Item nicht in die SQL-Abfrage übersetzen kann.
Die einzige Möglichkeit ist, die LINQ to Entities-Abfrage zu schreiben und dann eine LINQ to Objects-Abfrage in das Ergebnis zu schreiben, so wie es von Jacob empfohlen wurde.
Das Problem ist ein für das Entity-Framework spezifisches Problem, es ergibt sich nicht aus unserer Implementierung der Entity Framework-Unterstützung.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum