2016-07-28 11 views
3

Ich bin ziemlich neu in EF, und das ist etwas, das mich seit ein paar Tagen jetzt nervt:Wie erhält man eine EF-Abfrage, um das optimalste SQL zu kompilieren?

Ich habe eine User-Entität. Es hat einen übergeordneten WorkSpace, der eine Sammlung von Benutzern enthält. Jeder Benutzer verfügt in einer User.Schedules-Eigenschaft über eine Auflistung von untergeordneten Zeitplänen.

Ich bin durch die Objekte wie folgt navigieren:

var query = myUser.WorkSpace.Users.SelectMany(u => u.Schedules); 

Wenn die Ergebnisse der query Aufzählen (myUser ist eine Instanz von Benutzer, die zuvor unter Verwendung von .Find (Benutzer-ID) geladen worden ist), habe ich bemerkt, dass EF stellt für jeden Benutzer in WorkSpace eine Abfrage an die Datenbank. Benutzer

Warum kommt EF nicht die Ergebnisse in einer einzigen Abfrage, beginnend mit dem Primärschlüssel von myUser, und Beitritt mit allen beteiligten Tabellen?

Wenn ich etwas anderes direkt aus dem Kontext, wie dies zu tun, es funktioniert jedoch in Ordnung:

context.Users.Where(u => u.ID = userid).SelectMany(u => u.WorkSpace.Users.SelectMany(u => u.Schedules)) 

Gibt es etwas, was ich falsch mache?

+0

Verwenden Sie '.Join' also EF versteht, was Sie erreichen möchten –

+0

Aber was würde ich beitreten? Ich nehme an, ich würde etwas in meinem Kontext mitmachen, also ist es nicht viel anders als das zweite Beispiel, oder? –

+0

Ich bin wahrscheinlich nicht am besten, das zu lösen, weil ich keine Navigationseigenschaften verwende, bleibe ich klar. Ich benutze nur Schlüssel und Joins die ganze Zeit, und wenn die Abfrage ein bisschen komplexer wird, feuere ich in Code gespeicherte Prozeduren, mit Dapper oder etwas –

Antwort

2

Lassen Sie die erste Abfrage nehmen:

var query = myUser.WorkSpace.Users.SelectMany(u => u.Schedules); 

Wenn man sich die Typ aussehen der query Variable, werden Sie sehen, dass es IEnumerable<Schedule> ist, was bedeutet dies Objects Abfrage eine regelmäßige LINQ ist. Warum? Weil es von einem materialisierten Objekt ausgeht, dann einem anderen Objekt/einer anderen Sammlung usw. beitreten. Dies, kombiniert mit der EF-Lazy-Lade-Funktion, führt zu dem Mehrfach-Datenbank-Abfrageverhalten.

Wenn Sie das gleiche für die zweite Abfrage tun:

var query = context.Users.Where(u => u.ID = userid) 
    .SelectMany(u => u.WorkSpace.Users.SelectMany(u => u.Schedules)) 

Sie werden bemerken, dass der Typ des query jetzt IQueryable<Schedule> ist, was bedeutet, jetzt haben Sie eine LINQ to Entities-Abfrage. Dies liegt daran, dass weder context.Users noch andere Objekte/Sammlungen, die in der Abfrage verwendet werden, echte Objekte sind - sie sind nur Metadaten, die verwendet werden, um die Abfrage zu erstellen, auszuführen und zu materialisieren.

Zur Erinnerung, Sie machen nichts falsch. Das Lazy Loading funktioniert auf diese Weise. Wenn Sie das so genannte N+1 query Problem nicht interessieren, können Sie den ersten Ansatz verwenden. Wenn Sie sich kümmern, dann verwenden Sie die zweite.

+2

Der Schlüssel ist, dass EF Linq Abfragen auf 'Ausdruck ' Prädikaten die aren sind 't kompilierte Ausdrücke. EF liest diese Ausdrucksbäume, um das SQL zu erstellen. Da wir 'IEnumerable <>' und 'yield' für jede Zeile verwenden, erstellt EF für jede eine neue Abfrage, da sie nicht den gesamten Kontext kennt. –

+0

Ok danke, scheint klar, ich denke ich verstehe jetzt warum EF kann nicht verstehen, was ich im ersten Beispiel versuche.Ich interessiere mich für das N + 1-Problem (ich habe eine ziemlich große Anzahl von Kinddatensätzen, die ich so schnell wie möglich abrufen möchte), also sieht es so aus, als hätte ich keine andere Wahl, als den Kontext in meinem Code zu verwenden –