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();
}
es scheint jedoch, dass bei Verwendung von Include ein Leistungsabfall besteht
Das ist eine Untertreibung! Mehrere Include
s sprengen schnell das SQL-Abfrageergebnis, sowohl in der Breite als auch in der Länge. Warum das?
tl; dr Mehrere Include
s 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.
Include
sSagen wir, wir haben
Root
Root.Parent
Root.Children1
und Root.Children2
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:
SELECT
-Klausel ist die Summe aller Spalten in den vier TabellenDa 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 Include
s (Root
+ 2 untergeordnete Sammlungen): 15 Spalten * 200 Zeilen = 3000 Datenpunkte.
Drei Include
s (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 Include
s negativ beeinflusst.
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).
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 Include
s, 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.
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:
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.
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?