web-dev-qa-db-de.com

Entity-Framework-Code ist langsam, wenn Include () viele Male verwendet wird

Ich habe ein paar langsame Codes debuggt und es scheint, dass der Täter der unten abgebildete EF-Code ist. Es dauert 4-5 Sekunden, wenn die Abfrage zu einem späteren Zeitpunkt ausgewertet wird. Ich versuche, es in weniger als einer Sekunde zu erreichen. 

Ich habe dies mit dem SQL Server Profiler getestet, und es scheint, dass eine Reihe von SQL-Skripts ausgeführt werden. Es bestätigt auch, dass es 3-4 Sekunden dauert, bis der SQL Server die Ausführung abgeschlossen hat.

Ich habe andere ähnliche Fragen zur Verwendung von Include () gelesen und es scheint, dass es einen Leistungsnachteil gibt, wenn es verwendet wird. Ich habe versucht, den folgenden Code in mehrere verschiedene Abfragen aufzuteilen, aber es macht keinen großen Unterschied.

Hast du eine Idee, wie ich das Folgende schneller ausführen kann? 

Momentan zeigt die Web-App, an der ich gerade arbeite, nur einen leeren Iframe, während auf die Fertigstellung des Folgenden gewartet wird. Wenn ich keine schnellere Ausführungszeit bekomme, muss ich den iframe mit Daten aufteilen und teilweise mit einer anderen asynchronen Lösung laden. Alle Ideen hier wären auch dankbar!

using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
        {
            formInstance = context.FormInstanceSet
                                .Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormSectionDefinitions).Include(fs => fs.FormStateDefinitionEditableSections))
                                .Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormStateDefinitions))
                                .Includes(x => x.Include(fi => fi.FormSectionInstances).Include(fs => fs.FormFieldInstances).Include(ff => ff.FormFieldDefinition).Include(ffd => ffd.FormFieldMetaDataDefinition).Include(ffmdd => ffmdd.ComplexTypePropertyNames))
                                .Include(x => x.CurrentFormStateInstance)      
                                .Include(x => x.Files)
                                .FirstOrDefault(x => x.FormInstanceIdentifier == formInstanceIdentifier);

            scope.Complete();
        }
27
DSF

es scheint jedoch, dass bei Verwendung von Include ein Leistungsabfall besteht

Das ist eine Untertreibung! Mehrere Includes sprengen schnell das SQL-Abfrageergebnis, sowohl in der Breite als auch in der Länge. Warum das?

tl; dr Mehrere Includes sprengen die SQL-Ergebnismenge. Bald wird es günstiger, Daten durch mehrere Datenbankaufrufe zu laden, anstatt eine Mega-Anweisung auszuführen. Versuchen Sie, die beste Mischung aus Include- und Load-Anweisungen zu finden.

Wachstumsfaktor von Includes

Sagen wir, wir haben

  • root-Entität Root
  • übergeordnete Entität Root.Parent
  • untergeordnete Entitäten Root.Children1 und Root.Children2
  • eine LINQ-Anweisung Root.Include("Parent").Include("Children1").Include("Children2")

Dadurch wird eine SQL-Anweisung mit der folgenden Struktur erstellt:

SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1

UNION

SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2

Diese <PseudoColumns> bestehen aus Ausdrücken wie CAST(NULL AS int) AS [C2], und sie haben die gleiche Anzahl von Spalten in allen UNION- Abfragen. Der erste Teil fügt Pseudospalten für Child2 hinzu, der zweite Teil fügt Pseudospalten für Child1 hinzu.

Dies bedeutet für die Größe der SQL-Ergebnismenge:

  • Anzahl von column in der SELECT-Klausel ist die Summe aller Spalten in den vier Tabellen
  • Die Anzahl von rows ist die Summe der Datensätze in enthaltenen untergeordneten Sammlungen

Da die Gesamtzahl der Datenpunkte columns * rows ist, erhöht jeder zusätzliche Include die Gesamtzahl der Datenpunkte in der Ergebnismenge exponentiell. Lassen Sie mich das demonstrieren, indem Sie erneut Root nehmen, jetzt mit einer zusätzlichen Children3-Sammlung. Wenn alle Tabellen 5 Spalten und 100 Zeilen haben, erhalten wir:

Eine Include (Root + 1 untergeordnete Sammlung): 10 Spalten * 100 Zeilen = 1000 Datenpunkte.
Zwei Includes (Root + 2 untergeordnete Sammlungen): 15 Spalten * 200 Zeilen = 3000 Datenpunkte.
Drei Includes (Root + 3 untergeordnete Sammlungen): 20 Spalten * 300 Zeilen = 6000 Datenpunkte. 

Bei 12 Includes wären das 78000 Datenpunkte!

Wenn Sie dagegen alle Datensätze für jede Tabelle einzeln anstelle von 12 Includes erhalten, haben Sie 13 * 5 * 100 Datenpunkte: 6500, weniger als 10%!

Jetzt sind diese Zahlen etwas übertrieben, da viele dieser Datenpunkte null sind. Sie tragen also nicht viel zur tatsächlichen Größe der Ergebnismenge bei, die an den Client gesendet wird. Die Abfragegröße und die Aufgabe für das Abfrageoptimierungsprogramm werden jedoch durch die steigende Anzahl von Includes negativ beeinflusst.

Balance

Die Verwendung von Includes ist also ein heikles Gleichgewicht zwischen den Kosten von Datenbankaufrufen und dem Datenvolumen. Es ist schwer, eine Daumenregel zu geben, aber inzwischen können Sie sich vorstellen, dass das Datenvolumen die Kosten für zusätzliche Anrufe im Allgemeinen schnell übersteigt, wenn es mehr als ~ 3 Includes für untergeordnete Sammlungen gibt (für Väter Includes jedoch mehr Verbreite nur die Ergebnismenge).

Alternative

Die Alternative zu Include ist das Laden von Daten in separaten Abfragen:

context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);

Dadurch werden alle erforderlichen Daten in den Cache des Kontextes geladen. Während dieses Vorgangs führt EF relationship fixup aus, mit dem Navigationseigenschaften (Root.Children usw.) automatisch nach geladenen Entitäten aufgefüllt werden. Das Endergebnis ist identisch mit der Anweisung mit Includes, mit einem wichtigen Unterschied: Die untergeordneten Sammlungen werden im Entitätszustandsmanager nicht als geladen markiert. EF versucht daher, beim Laden auf Lazy Load zu triggern. Deshalb ist es wichtig, das Lazy-Loading auszuschalten.

In der Realität müssen Sie herausfinden, welche Kombination von Include- und Load-Anweisungen für Sie am besten geeignet ist.

47
Gert Arnold

Ich habe ein ähnliches Problem mit einer Abfrage, die über 15 "Include" -Anweisungen enthielt und in 7 Minuten ein Ergebnis von mehr als 2 Millionen Zeilen generierte.

Die Lösung, die für mich funktioniert hat, war:

  1. Lazy Loading deaktiviert
  2. Deaktivierte automatische Erkennung von Änderungen
  3. Teilen Sie die große Abfrage in kleine Teile auf

Ein Beispiel finden Sie unten:

public IQueryable<CustomObject> PerformQuery(int id) 
{
 ctx.Configuration.LazyLoadingEnabled = false;
 ctx.Configuration.AutoDetectChangesEnabled = false;

 IQueryable<CustomObject> customObjectQueryable = ctx.CustomObjects.Where(x => x.Id == id);

 var selectQuery = customObjectQueryable.Select(x => x.YourObject)
                                                  .Include(c => c.YourFirstCollection)
                                                  .Include(c => c.YourFirstCollection.OtherCollection)
                                                  .Include(c => c.YourSecondCollection);

 var otherObjects = customObjectQueryable.SelectMany(x => x.OtherObjects);

 selectQuery.FirstOrDefault();
 otherObjects.ToList();

 return customObjectQueryable;
 }

IQueryable wird benötigt, um die gesamte Filterung auf der Serverseite durchzuführen. IEnumerable würde die Filterung im Speicher durchführen und dies ist ein sehr zeitaufwendiger Prozess. Entity Framework repariert alle Assoziationen im Speicher.

0
Andrei Petrut

Haben Sie die Beziehungen zwischen allen Entitäten, die Sie einschließen möchten, korrekt konfiguriert? Wenn mindestens eine Entität keine Beziehung zu einigen anderen Entitäten hat, kann EF keine komplexe Abfrage mithilfe der SQL-Join-Syntax erstellen. Stattdessen führt sie so viele Abfragen aus, wie viele "Einschlüsse" Sie haben. Das führt natürlich zu Leistungsproblemen. Könnten Sie bitte die exakte Abfrage (-es) posten, die EF generiert, um die Daten zu erhalten?

0
drcolombo