Top pro Gruppe: Take (1) funktioniert, aber FirstOrDefault () nicht?

9

Ich benutze EF 4.3.1 ... gerade aktualisiert auf 4.4 (Problem bleibt) mit Datenbank-ersten POCO-Entitäten, die vom EF 4.x DbContext Generator . Ich habe die folgende Datenbank namens 'Wiki' (SQL-Skript zum Erstellen von Tabellen und Daten ist hier ):

Wenn ein Wiki-Artikel bearbeitet wird, wird anstelle des Datensatzes, der aktualisiert wird, die neue Revision als neuer Datensatz eingefügt, wobei der Revisionszähler inkrementiert wird. In meiner Datenbank gibt es einen Autor, "John Doe", der zwei Artikel hat, "Artikel A" und "Artikel B", wobei Artikel A zwei Versionen (1 und 2) hat, aber Artikel B nur eine Version hat. p>

Ich habe sowohl das Laden von Lazy als auch die Proxy-Erstellung deaktiviert ( hier ist die Beispiellösung, die ich verwende mit LINQPad). Ich möchte die neuesten Überarbeitungen von Artikeln erhalten, die von Leuten erstellt wurden, deren Name mit "John" beginnt, also mache ich die folgende Abfrage:

%Vor%

Dies erzeugt das falsche Ergebnis und ruft nur den ersten Artikel ab:

Wenn Sie eine kleine Änderung in der Abfrage vornehmen, indem Sie .FirstOrDefault() durch .Take(1) ersetzen, erhalten Sie folgende Abfrage:

%Vor%

Überraschenderweise liefert diese Abfrage korrekte Ergebnisse (wenn auch mit mehr Verschachtelung):

Ich nahm an, dass EF leicht unterschiedliche SQL-Abfragen erzeugt, eine, die nur die letzte Revision eines einzelnen Artikels zurückgibt, die andere gibt die letzte Revision aller Artikel zurück. Das hässliche SQL, das von den beiden Abfragen generiert wird, unterscheidet sich nur geringfügig (vergleiche: SQL für .FirstOrDefault () gegen SQL für .Take (1) ), , aber beide geben das korrekte Ergebnis zurück:

.FirstOrDefault()

.Take(1) (Spaltenreihenfolge für einfachen Vergleich neu angeordnet)

Der Täter ist daher nicht das generierte SQL, sondern die Interpretation des Ergebnisses durch EF. Warum interpretiert EF das erste Ergebnis in einer einzigen Article -Instanz, während es das zweite Ergebnis als zwei Article -Instanzen interpretiert? Warum gibt die erste Abfrage falsche Ergebnisse zurück?

BEARBEITEN: Ich habe einen Fehlerbericht über Connect geöffnet. Bitte aktualisieren Sie es, wenn Sie es für wichtig halten, dieses Problem zu beheben.

    
Allon Guralnek 27.08.2012, 08:10
quelle

3 Antworten

3

Betrachten Sie: Ссылка
Ссылка
Es gibt sehr schöne Erklärungen, wie Take funktioniert (faul, früh brekaing), aber keine von FirstOrDefault. Außerdem würde ich, wenn ich die Erklärung von Take betrachte, "animieren", dass die Abfragen mit Take die Anzahl der Zeilen aufgrund eines versuchen, die faule Auswertung in SQL zu emulieren, und Ihr Fall zeigt an, dass es andersherum ist! Ich verstehe nicht, warum Sie einen solchen Effekt beobachten.

Es ist wahrscheinlich nur implementierungsspezifisch. Für mich könnten sowohl Take (1) als auch FirstOrDefault wie TOP 1 aussehen, aber aus funktioneller Sicht kann es einen kleinen Unterschied in ihrer "Faulheit" geben: eine Funktion kann werte alle Elemente aus und kehre zuerst zurück, der zweite kann zuerst auswerten, dann zurückgeben und die Bewertung unterbrechen. Es ist nur ein "Hinweis" auf was passiert sein könnte. Für mich ist es ein Unsinn, weil ich keine Dokumente zu diesem Thema sehe und im Allgemeinen bin ich mir sicher, dass beide Take / FirstOrDefault träge sind und nur die ersten N Elemente auswerten sollten.

Im ersten Teil Ihrer Abfrage ist die group.Select + orderBy + TOP1 eine "klare Angabe", dass Sie sich für die einzelne Zeile mit dem höchsten "Wert" in einer Spalte pro Gruppe interessieren - aber tatsächlich gibt es keine einfache Methode, dies in SQL zu deklarieren , daher ist die Angabe für die SQL-Engine und für die EF-Engine nicht so eindeutig.

Was mich anbetrifft, könnte das Verhalten, das Sie präsentieren, darauf hinweisen, dass der FirstOrDefault vom EF-Translator eine Schicht innerer Abfragen zu weit nach oben "propagiert" wurde, wie bei der Articles.GroupBy () (Sind Sie sicher, dass Sie nicht fehl am Platz waren?) parens adder OrderBy? :)) - und das wäre ein Fehler.

Aber -

Da der Unterschied irgendwo in der Bedeutung und / oder der Reihenfolge der Ausführung liegen muss, sehen wir, was EF über die Bedeutung Ihrer Anfrage erraten kann. Wie erhält die Autorin ihre Artikel? Wie weiß die EF, welchen Artikel sie an Ihren Autor binden soll? Natürlich, das Nav-Eigentum. Aber wie kommt es, dass nur einige Artikel vorinstalliert sind? Scheint einfach - die Abfrage gibt einige Ergebnisse mit Come-Spalten zurück, Spalten beschreiben den gesamten Autor und ganze Artikel, lassen Sie sie also Autoren und Artikeln zuordnen und lassen sie einander über die Navigationsschlüssel zuordnen. OK. Aber füge die komplexe Filterung hinzu ...?

Mit einem einfachen Filter wie "Bis-Datum" ist es eine einzelne Unterabfrage für alle Artikel, Zeilen werden nach Datum abgeschnitten und alle Zeilen werden verbraucht. Aber wie wäre es mit dem Schreiben einer komplexen Abfrage, die mehrere Zwischenreihenfolgen verwenden und mehrere Teilmengen von Artikeln erzeugen würde? Welche Teilmenge sollte an den resultierenden Autor gebunden sein? Vereinigung aller von ihnen? Dies würde alle Woeh-Like-Klauseln der obersten Ebene aufheben. Der erste von ihnen? Unsinn, erste Unterabfragen neigen dazu, Vermittler zu sein. Wenn also eine Abfrage als eine Menge von Unterabfragen mit ähnlicher Struktur betrachtet wird, die alle als Datenquelle für ein teilweises Laden einer nav-Eigenschaft verwendet werden könnten, wird höchstwahrscheinlich nur die letzte Unterabfrage als das tatsächliche Ergebnis genommen. Das ist alles abstraktes Denken, aber es hat mich darauf aufmerksam gemacht, dass Take () versus FirstOrDefault und ihre gesamte Verbindung gegen LeftJoin die Reihenfolge der Scan-Ergebnisse ändern kann, und irgendwie wurde Take () irgendwie optimiert und in einem Scan erledigt über das ganze Ergebnis, also alle Artikel des Autors gleichzeitig zu besuchen, und der FirstOrDefault wurde als Direktscan for each author * for each title-group * select top one and check count and substitue for null ausgeführt, was oft zu kleinen Ein-Objekt-Sammlungen von Artikeln pro Autor geführt hatte und somit zu einem Ergebnis führte - nur von der letzte Titel-Gruppierung besucht.

Das ist die einzige Erklärung, die ich mir vorstellen kann, außer dem offensichtlichen "BUG!" schreien. Als LINQ-Benutzer ist es für mich immer noch ein Fehler. Entweder sollte eine solche Optimierung überhaupt nicht stattgefunden haben, oder sie sollte auch den FirstOrDef enthalten - so wie Take (1) .DefaultIfEmpty (). Heh, übrigens - hast du das probiert? Wie gesagt, Take (1) ist nicht identisch mit FirstOrDefault aufgrund der Bedeutung von JOIN / LEFTJOIN - aber Take (1) .DefaultIfEmpty () ist eigentlich semantisch das Gleiche. Es könnte Spaß machen, zu sehen, welche SQL-Abfragen bei SQL erzeugt werden und was in EF-Layern resultiert.

Ich muss zugeben, dass die Auswahl der verwandten Entitäten im Partial-Loading mir nie klar war und ich die Partial-Loading seit langer Zeit nicht benutzt habe Abfragen, so dass die Ergebnisse und Gruppierungen explizit definiert sind (*). Daher könnte ich einfach einige wichtige Aspekte / Regeln / Definitionen ihrer inneren Arbeit vergessen haben und vielleicht, dh. Es ist tatsächlich, jeden Datensatz in der Ergebnismenge auszuwählen (nicht nur die letzte Untersammlung, wie ich es jetzt beschrieben habe). Wenn ich etwas vergessen hätte, wäre alles, was ich gerade beschrieben habe, offensichtlich falsch.

(*) In Ihrem Fall würde ich die Article.AuthorID auch zu einer nav-Eigenschaft machen (public Author Author get set) und dann die Abfrage ähnlich wie flat / pipeline umschreiben, wie:

%Vor%

und füllen Sie dann die View mit Paaren von Author und Arts separat aus, anstatt zu versuchen, den Autor teilweise zu füllen und nur Autor zu verwenden. Übrigens. Ich habe es nicht gegen EF und SServer getestet, es ist nur ein Beispiel dafür, wie man die Abfrage im Falle von JOINs auf den Kopf stellt und die Unterabfragen abflacht und für LEFTJOINs unbrauchbar ist. Wenn Sie also auch die Autoren ohne Artikel, muss es von den Autoren wie Ihre ursprüngliche Abfrage beginnen.

Ich hoffe, dass diese losen Gedanken ein wenig dazu beitragen, "warum" zu finden.

    
quetzalcoatl 27.08.2012, 10:01
quelle
2

Die Methode FirstOrDefault() ist momentan, während die andere ( Take(int) ) bis zur Ausführung zurückgestellt wird.

    
AgentFire 27.08.2012 17:59
quelle
0

Wie in der vorherigen Antwort habe ich versucht, über das Problem nachzudenken - ich habe resigniert, und ich schreibe ein anderes :) Nachdem ich es nochmal angeschaut habe, denke ich, dass es ein Fehler ist. Ich denke, Sie sollten einfach Take verwenden und den Fall an Microsoft Connect senden und überprüfen, was sie dazu sagen.

Folgendes habe ich gefunden: Ссылка

Die Antwort von "Microsoft 2011-09-22 at 16:07" beschreibt im Detail einige Optimierungsmechanismen in EF. An einigen Stellen wird gesagt, dass man skip / take / orderby umsortieren muss und manchmal erkennt die Logik einige Konstrukte nicht. Ich denke, Sie sind gerade auf einen anderen Eckfall gestoßen, der noch nicht richtig in der'Organisation durch Heben 'verzweigt ist. Alles in allem, in der resultierenden SQL hast du Select-Top-1 in einer Order-By, und der Schaden sieht genauso aus, als würde man die 'Top 1' um eine Stufe zu hoch heben!

    
quetzalcoatl 27.08.2012 16:44
quelle