Warum dein Entity Framework langsam ist

Oft hört man ja, dass das Entity Framework zwar sehr komfortabel, aber auch sehr langsam sei. Bei Update und Insert Operationen ist dies tatsächlich der Fall und es gibt schon einige Frameworks, die sich Bulk Inserts und Updates angenommen haben. Wenn es allerdings um ein Get geht, ist der Flaschenhals fast immer der Programmierer vorm Bildschirm. Dieser Post soll meine Lernkurve von 2 Jahren Entity Framework Nutzung wiederspiegeln und hoffentlich einigen aufzeigen, wie sie ihre Abfragen zig-fach schneller machen können.

In diesem Post geht es um das Entity Framework 6.x, nicht um die (zu diesem Zeitpunkt) aktuelle Beta vom Entity Framework 7, wobei vieles hier gesagte auch auf die 7er Version zutrifft.
Wir nutzen in diesem Beispiel eine Azure Datenbank mit den Adventure Works Daten. Wer dies selbst nachvollziehen möchte, kann sich auf Azure direkt eine Datenbank mit den Beispieldatensätzen erzeugen lassen. Durch diese Konstellation erreichen wir 2 Dinge:
1. Eine gewisse Round Trip Time zum Server
2. Eine nicht zu kleine Beispieldatenbank

Das Ausgangsszenario

Wir wollen (warum auch immer) die Summe aller Einzelpreise aller Käufe aller Kunden berechnen.
Es gibt effizientere Methoden, dies zu tun, als Beispiel zeigt es aber sehr schön die Implikationen von Verschachtelung.

var sum = 0m;
foreach (var customer in customers)
{
    foreach (var salesOrderHeader in customer.SalesOrderHeader)
    {
        foreach (var detail in salesOrderHeader.SalesOrderDetail)
        {
            sum += detail.UnitPrice;
        }
    }
}
Console.WriteLine($"Sum is {sum:N2}");

Dabei Messen wir 2 Zeiten:
1. Die Query Zeit
2. Die lokale Berechnungszeit der verschachtelten foreach-Schleifen.

Außerdem schauen wir uns an, wie viele SQL SELECT Statements genutzt wurden um zum Ergebnis zu kommen.

Der erste Versuch: IQueryable

Da wir einfach ein foreach haben und über alle Einträge der Customer Tabelle iterieren wollen, können wir einfach direkt über die Datenbanktabelle iterieren.

var customers = context.Customer;

Sum is 353.504,37 (korrekt, dies ist nur zum überprüfen, dass wir auch wirklich alle Entities geladen haben)
Used 1022 SQL SELECT Statements
Get took 0ms
Output took 51815ms

Warum hat die Berechnung so lange gedauert, wo doch eigentlich die Get Operation sehr schnell war, mag man sich nun denken. Dies liegt daran, dass initial im Get noch nichts getan wird. Es wird lediglich ein Verweis auf die korrekte Datenbanktabelle bereitgestellt, der eigentliche Abruf der Daten erfolgt versteckt mit der foreach-Schleife.

Auch nicht schneller: IEnumerable

Also denken wir uns, wenn ein IQueryable (context.Customer ist ein IQueryable) dafür sorgt, dass die Abfrage erst zu einem späteren Zeitpunkt geschieht, sind wir schlauer in nutzen ein IEnumerable, denn im Gegensatz zu einem IQueryable repräsentiert ein IEnumerable eher Daten im Speicher.

var customers = context.Customer.AsEnumerable();

Sum is 353.504,37
Used 1022 SQL SELECT Statements
Get took 0ms
Output took 50568ms

Doch was sehen wir da: Get ist immer noch schnell und die foreach-Schleifen sind immer noch langsam. Die Abfrage ist zwar fast 3s schneller, was aber auf normale Netzschwankungen zurückzuführen ist und sich ausgleichen würde, sobald man beide Abfragen mehrmals durchführt.

Gut getrennt: ToList

Um Get und Output nun wirklich zu trennen, können wir ToList() nutzen. Das sorgt dafür, dass die Tabelle tatsächlich in den Speicher geladen wird.

var customers = context.Customer.ToList();

Sum is 353.504,37
Used 1022 SQL SELECT Statements
Get took 309ms
Output took 46930ms

Nun hat unser ToList() zwar dafür gesorgt, dass das initiale Get direkt ausgeführt wird, allerdings sind die foreach-Schleifen immer noch sehr langsam. Das liegt an dem Feind jeder Entity Framework Abfrage und nennt sich Lazy Loading. Was wir dagegen tun könne, seht ihr im nächsten Post.

2 Gedanken zu „Warum dein Entity Framework langsam ist

  1. Pingback: Warum dein Entity Framework langsam ist Teil 2 - Cocktails and Code

  2. Pingback: Warum dein Entity Framework langsam ist Teil 3 - Cocktails and Code

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.