Entity Framework-Abfrage langsam, aber dasselbe SQL in SqlQuery ist schnell

91

Ich sehe eine wirklich seltsame Leistung im Zusammenhang mit einer sehr einfachen Abfrage mit Entity Framework Code-First mit .NET Framework Version 4. Die LINQ2Entities-Abfrage sieht folgendermaßen aus:

 context.MyTables.Where(m => m.SomeStringProp == stringVar);

Die Ausführung dauert über 3000 Millisekunden. Das generierte SQL sieht sehr einfach aus:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = '1234567890'

Diese Abfrage wird fast sofort ausgeführt, wenn sie über Management Studio ausgeführt wird. Wenn ich den C # -Code ändere, um die SqlQuery-Funktion zu verwenden, wird er in 5-10 Millisekunden ausgeführt:

 context.MyTables.SqlQuery("SELECT [Extent1].[ID] ... WHERE [Extent1].[SomeStringProp] = @param", stringVar);

Bei genau derselben SQL werden die resultierenden Entitäten in beiden Fällen nachverfolgt, aber es gibt einen wilden Leistungsunterschied zwischen den beiden. Was gibt?

Brian Sullivan
quelle
2
Ich gehe davon aus, dass es zu Verzögerungen bei der Initialisierung kommt - wahrscheinlich beim Kompilieren. Siehe MSDN:Performance Considerations for Entity Framework 5
Nicholas Butler
Ich habe versucht, Ansichten vorab zu generieren, und es scheint nicht zu helfen. Außerdem wurde vor der langsamen eine weitere EF-Abfrage ausgeführt, um Initialisierungsaufgaben auszuschließen. Die neue Abfrage wurde schnell ausgeführt, die langsame wurde immer noch langsam ausgeführt, obwohl das Aufwärmen des Kontexts während der ersten Abfrage erfolgte.
Brian Sullivan
1
@marc_s - Nein, SqlQuery gibt eine vollständig materialisierte und von Änderungen verfolgte Entitätsinstanz zurück. Siehe msdn.microsoft.com/en-us/library/…
Brian Sullivan
Inliniert das generierte SQL für Ihre EF-Abfrage tatsächlich den Parameterwert oder verwendet es einen Parameter? Dies sollte die Abfragegeschwindigkeit für eine einzelne Abfrage nicht beeinflussen, kann jedoch dazu führen, dass der Abfrageplan im Laufe der Zeit auf dem Server aufgebläht wird.
Jim Wooley
Haben Sie versucht, dieselbe Abfrage zweimal / mehrmals auszuführen? Wie lange hat es beim zweiten Laufen gedauert? Haben Sie dies in .NET Framework 4.5 versucht? In .NET Framework 4.5 gibt es einige EF-bezogene Leistungsverbesserungen, die helfen könnten.
Pawel

Antworten:

93

Fand es. Es stellt sich heraus, dass es sich um ein Problem mit SQL-Datentypen handelt. Die SomeStringPropSpalte in der Datenbank war ein Varchar, aber EF geht davon aus, dass .NET-Zeichenfolgentypen nvarchars sind. Der resultierende Übersetzungsprozess während der Abfrage der DB, um den Vergleich durchzuführen, dauert lange. Ich glaube, EF Prof hat mich hier ein bisschen in die Irre geführt. Eine genauere Darstellung der ausgeführten Abfrage wäre die folgende:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = N'1234567890'

Der resultierende Fix besteht also darin, das Code-First-Modell mit Anmerkungen zu versehen und den richtigen SQL-Datentyp anzugeben:

public class MyTable
{
    ...

    [Column(TypeName="varchar")]
    public string SomeStringProp { get; set; }

    ...
}
Brian Sullivan
quelle
1
Schöne Untersuchung. Ihre Anfrage litt unter "impliziter Konvertierung", wie hier erklärt: brentozar.com/archive/2012/07/…
Jaime
Ersparte mir ein paar Stunden Debugging. Das war genau das Problem.
Cody
1
In meinem Fall verwende ich EDMX mit einer Legacy-Datenbank, die varcharfür alles verwendet wird, und tatsächlich war dies das Problem. Ich frage mich, ob ich einen EDMX erstellen kann, um varchar für alle String-Spalten zu berücksichtigen.
Alisson
1
Großartiger Mann. Aber @Jaime, was wir für den ersten Datenbankansatz tun sollten, da alles (z. B. Datenanmerkungen auf Datenbankmodellen) nach dem Aktualisieren des EF-Modells aus der Datenbank gelöscht wird.
Nauman Khan
43

Der Grund für die Verlangsamung meiner in EF gestellten Abfragen war der Vergleich nicht nullbarer Skalare mit nullbaren Skalaren:

long? userId = 10; // nullable scalar

db.Table<Document>().Where(x => x.User.Id == userId).ToList() // or userId.Value
                                ^^^^^^^^^    ^^^^^^
                                Type: long   Type: long?

Diese Abfrage dauerte 35 Sekunden. Aber so ein winziges Refactoring:

long? userId = 10;
long userIdValue = userId.Value; // I've done that only for the presentation pursposes

db.Table<Document>().Where(x => x.User.Id == userIdValue).ToList()
                                ^^^^^^^^^    ^^^^^^^^^^^
                                Type: long   Type: long

gibt unglaubliche Ergebnisse. Die Fertigstellung dauerte nur 50 ms. Es ist möglich, dass es sich um einen Fehler in EF handelt.

Cryss
quelle
13
Das ist so komisch
Daniel Cardenas
1
OH MEIN GOTT. Dies kann anscheinend auch passieren, wenn die Schnittstellen IUserId.Id das Problem bei mir verursacht haben, aber die erste Zuordnung der ID zu einer Ganzzahl funktioniert ... muss ich jetzt alle Abfragen in meiner 100.000-Zeilen-Anwendung überprüfen?
Dirk Boer
Wurde dieser Fehler gemeldet? Es ist immer noch in der neuesten Version 6.2.0
Dirk Boer
2
Das gleiche Problem tritt auch bei EF Core auf. Danke, dass du das gefunden hast!
Yannickv
Ein weiterer Vorschlag besteht darin, die Variable zu verarbeiten, bevor sie in den LINQ-Ausdruck eingefügt wird. Andernfalls wird die generierte SQL viel länger und langsamer. Ich habe festgestellt, dass Trim () und ToLower () im LINQ-Ausdruck enthalten sind, was mich nervt.
Samheihey
4

Ich hatte das gleiche Problem (die Abfrage ist schnell, wenn sie von SQL Manager ausgeführt wird), aber wenn sie von EF ausgeführt wird, läuft das Zeitlimit ab.

Es stellt sich heraus, dass die Entität (die aus der Ansicht erstellt wurde) falsche Entitätsschlüssel hatte. Die Entität hatte also doppelte Zeilen mit denselben Schlüsseln, und ich denke, sie musste im Hintergrund gruppiert werden.

Vladimir Gedgafov
quelle
2

Ich bin auch auf eine komplexe ef-Abfrage gestoßen. Ein Fix für mich, der eine 6-Sekunden-EF-Abfrage auf die erzeugte SQL-Abfrage von weniger als einer Sekunde reduzierte, bestand darin, das verzögerte Laden zu deaktivieren.

Um diese Einstellung zu finden (ef 6), gehen Sie zur .edmx-Datei und suchen Sie unter Eigenschaften -> Codegenerierung -> Lazy Loading Enabled. Auf false setzen.

Massive Leistungsverbesserung für mich.

user2622095
quelle
4
Das ist cool, hat aber nichts mit der Posterfrage zu tun.
Jace Rhea
2

Ich hatte auch dieses Problem. Es stellte sich heraus, dass der Schuldige in meinem Fall das Schnüffeln von SQL-Server- Parametern war .

Der erste Hinweis darauf, dass mein Problem tatsächlich auf das Sniffing von Parametern zurückzuführen war, war, dass das Ausführen der Abfrage mit "Arithabort deaktivieren" oder "Arithabort setzen auf" drastisch unterschiedliche Ausführungszeiten in Management Studio ergab. Dies liegt daran, dass ADO.NET standardmäßig "Arithabort deaktivieren" verwendet und Management Studio standardmäßig "Arithabort festlegen" verwendet. Der Abfrageplan-Cache enthält abhängig von diesem Parameter unterschiedliche Pläne.

Ich habe das Zwischenspeichern von Abfrageplänen für die Abfrage deaktiviert. Die Lösung finden Sie hier .

Oskar Sjöberg
quelle