Warum dein Entity Framework langsam ist Teil 3

In Teil 1 haben wir uns den generellen Aufbau angeschaut und mit IQueryable, IEnumerable und ToList die Basis gelegt.
In Teil 2 haben wir anschließend erste Performanceoptimierung mit Include und einzelnen Abfragen durchgeführt.
In diesem Teil werden wir nun die Entity Framework Abfragen noch parallelisieren.
Weiterlesen

Warum dein Entity Framework langsam ist Teil 2

In Teil 1 haben wir uns den generellen Aufbau angeschaut und mit IQueryable, IEnumerable und ToList die Basis gelegt. Nun wollen wir uns anschauen, wie wir die Abfrage tatsächlich schneller bekommen.

Weniger Abfragen: Include

Mit ToList haben wir bereits die Customer Tabelle in den Speicher bekommen. Unsere verschachtelten foreach-Schleifen sorgen nun allerdings für Lazy Loading, d.h. es wird mit jeder Verschachtelungstiefe erneut eine Anfrage an die Datenbank gesendet. Es werden also in eienr Abfrage alle Customer Objekte geladen. In der nächsten wird zu einem Customer alle SalesOrderHeader Objekte geladen. Zum Schluss werden dann noch zu jedem SalesOrderHeader alle SalesOrderDetail Objekte geladen. Daraus resultieren die bisherigen 880 SQL SELECT Statements. Mit Include können wir diese Referenzen in einer Abfrage laden.

var customers = context.Customer
    .Include(c => c.SalesOrderHeader.Select(h => h.SalesOrderDetail.Select(d => d.Product)))
    .ToList();

Sum is 353.504,37
Used 1 SQL SELECT Statements
Get took 3275ms
Output took 1ms

Wir haben nun also mit einer Abfrage alle Tabellen auf einmal geladen. Dies kann nützlich sein (besonders für UPDATE Statements), hat aber einen gewissen Performance Impact, welcher um so schlimmer wird, je mehr verschachtelte Tabellen geladen werden. Das liegt daran, dass das generierte SQL ein JOIN über diese Tabellen ist. Haben wir nun 1 Customer mit 2 SalesOrderHeader mit jeweils 3 SalesOrderDetail, werden 1 * 2 * 3 = 6 Zeilen aus der Datenbank geladen. Jede dieser Zeilen hat für den Customer die gleichen Informationen und auch die SalesOrderHeader sind doppelt vertreten. Man kann sich das so vorstellen:
[Customer][SalesOrderHeader1][SalesOrderDetail1][Product1]
[Customer][SalesOrderHeader1][SalesOrderDetail2][Product2]
[Customer][SalesOrderHeader1][SalesOrderDetail3][Product3]
[Customer][SalesOrderHeader2][SalesOrderDetail4][Product4]
[Customer][SalesOrderHeader2][SalesOrderDetail5][Product5]
[Customer][SalesOrderHeader2][SalesOrderDetail6][Product6]

Join in Memory: Mehrere Abfragen

Um das Problem mit den redundanten Daten zu umgehen, machen wir einfach mehrere Abfragen, jeweils eine pro Tabelle, die wir benötigen.

var customers = context.Customer.ToDictionary(c => c.CustomerID);
var headers = context.SalesOrderHeader.ToDictionary(h => h.SalesOrderID);
var details = context.SalesOrderDetail.ToDictionary(d => d.SalesOrderDetailID);
var products = context.Product.ToDictionary(p => p.ProductID);
RelationshipFixup(customers, headers, details, products);
private static void RelationshipFixup(IDictionary<int, Customer> customers, IDictionary<int, SalesOrderHeader> headers, IDictionary<int, SalesOrderDetail> details, IDictionary<int, Product> products)
{
    foreach (var header in headers.Values)
    {
        header.Customer = customers[header.CustomerID];
        header.Customer.SalesOrderHeader.Add(header);
    }

    foreach (var detail in details.Values)
    {
        detail.SalesOrderHeader = headers[detail.SalesOrderID];
        detail.SalesOrderHeader.SalesOrderDetail.Add(detail);

        detail.Product = products[detail.ProductID];
        detail.Product.SalesOrderDetail.Add(detail);
    }
}

Sum is 353.504,37
Used 1025 SQL SELECT Statements
Get took 10563ms
Output took 40859ms

Huch, da ist aber etwas gewaltig schief gelaufen. Das Problem ist, dass wir die Tabellen alle auf demselben context Objekt holen. Standardmäßig ist Change Tracking und Proxy Creation im Entity Framework 6.x angeschaltet (in 7 ist es standardmäßig ausgeschaltet). Dies kostet umso mehr Performance, je mehr Daten in einem Context geladen werden.

Diesmal korrekt: Mehrere Abfragen

Um das Problem zu beheben, sorgen wir dafür, dass Change Tracking und Proxy creation ausgeschaltet werden. Wenn man dies tut, muss man sich im klaren darüber sein, dass man auch Lazy Loading ausschaltet. Hat man früher mit Lazy Loading gearbeitet, kann es nun zu Fehlern im Programm kommen, da nun alles explizit geladen werden muss. Dadurch erhält man allerdings die volle Kontrolle über sein Programm zurück;

IDictionary<int, Customer> customers;
using (var context = GetContextForGet(counter))
{
    customers = context.Customer.ToDictionary(c => c.CustomerID);
    var headers = context.SalesOrderHeader.ToDictionary(h => h.SalesOrderID);
    var details = context.SalesOrderDetail.ToDictionary(d => d.SalesOrderDetailID);
    var products = context.Product.ToDictionary(p => p.ProductID);

    RelationshipFixup(customers, headers, details, products);
}
private static EfPerformanceContext GetContextForGet()
{
    var context = new EfPerformanceContext();

    context.Configuration.AutoDetectChangesEnabled = false;
    context.Configuration.ProxyCreationEnabled = false;

    return context;
}

Sum is 353.504,37
Used 4 SQL SELECT Statements
Get took 1305ms
Output took 0ms

Wir sehen, dass zwar mehr SQL Select Abfragen, als bei Include verwendet wurden, aber der Abfragen insgesamt schneller waren. Um unsere Entity Framework Abfragen jetzt noch schneller zu machen, werden wir im nächsten Teil die Abfragen parallelisieren.

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.
Weiterlesen

wintron7_Windows10_1511

Windows 10 TH2 1511 auf TrekStor SurfTab wintron 7.0 v2 installieren

In meiner neuen Wohnung wird es keine Lichtschalter mehr geben, sondern nur noch Tablets,
welche stattdessen an der Wand hängen werden.
Ich habe mich hierbei für das wintron 7.0 von TrekStor entschieden.
7″ ist eine gute Größe für die Aufgabe und der Preis ist mit ca. 45€ auch extrem günstig für ein Tablet mit vollwertigem Windows.

Ursprünglich wurde das Tablet mit Windows 8.1 ausgeliefert.
Mittlerweile gibt es die v2 des wintron 7.0, welches mit Windows 10 (RTM) ausgeliefert wird.
Möchte man nun auf das November Update Threshold 2 1511 aktualisieren, bekommt man ersteinmal eine Fehlermeldung, dass man nicht genügend freien Speicher hat.
Man kann nun mittels Systembereinigung, kleinerer Auslagerungsdatei, löschen von allem, was nicht benötigt wird und zusätzlich einem externen Medium mit mind. 16Gb Speicher dennoch aktualisieren.
Da ich allerdings gleich mehrere dieser Tablets gekauft habe, ist diese Prozedur deutlich zu aufwändig.
Deshalb entschied ich mich für die komplette Neuinstallation von Windows 10 TH2 1511.
Weiterlesen

SignalR auf dem Arduino Part1: Das Protokoll verstehen

Für mein Smart-Home Projekt sollen einige Arduinos als Temperatursensoren zum Einsatz kommen.
Da als Bus SignalR genutzt wird, es aber für den Arduino noch keine SignalR Bibliothek gibt, muss ich selbst etws basteln.
Das Ergebnis soll hierbei nicht eine vollständige SignalR Implementation für den Arduino sein, sondern nur soweit funktionieren, dass ich mittels Websockets Nachrichten an den Server schicken kann, um so die Temperatur zu übermitteln.

Für den Arduino gibt es bereits HTTP, Websocket und JSON Bibliotheken.
Diese müssen aber zu einer SignalR Bibliothek verbunden werden.
Als erstes muss also das Protokoll verstanden werden.
Weiterlesen