EF6 oder EF Core 10? Wir haben dieselben Abfragen auf beiden gemessen
EF6 oder EF Core 10? Wir haben Abfragen, Include, Inserts und Updates auf SQL Server gemessen. EF Core war bis zu 17-mal schneller.
Vlado Pandžić · Gründer · Senior .NET-Architekt
Veröffentlicht · 9 Min. Lesezeit
Inhalt
„Ist EF Core wirklich schneller, oder steht das nur in den Release Notes?“ Diese Frage stellt sich jedes Team mit einer älteren Entity-Framework-6-Anwendung, bevor es entscheidet, ob sich die Migration lohnt. Microsoft veröffentlicht zwar Benchmarks, aber die haben selten etwas mit den Abfragen in einem echten Bestell- oder Rechnungssystem zu tun.
Also haben wir genau die Abfragen geschrieben, die in so einem System stecken, und sie unverändert auf drei Varianten laufen lassen: EF6 auf .NET Framework 4.8, wo die meisten EF6-Anwendungen heute noch laufen, EF6 auf .NET 10, was seit EF 6.4 möglich ist, und EF Core 10 auf .NET 10. Dieselbe Datenbank, dieselben Daten, dieselben Entitätsklassen.
Die Kurzfassung: EF Core 10 war 1,6- bis 17-mal schneller als EF6 auf .NET Framework und brauchte bis zu 28-mal weniger Speicher. Der größte Abstand lag beim Speichern von Daten, nicht beim Lesen. Und EF6 auf .NET 10 hat sich als sinnvoller Zwischenschritt erwiesen: schneller als auf .NET Framework, ohne dass man eine einzige Abfrage anfassen muss.
So haben wir gemessen
- Die Datenbank: SQL Server (LocalDB) auf derselben Maschine, mit 1.000 Kunden, 10.000 Bestellungen mit einem 2 KB großen Notizfeld und 50.000 Bestellpositionen. Ein Viertel der Bestellungen ist offen.
- Dieselben Entitätsklassen für alle drei Varianten, auf dieselben Tabellen gemappt. EF6 6.5.1 und EF Core 10.
- Das Werkzeug: BenchmarkDotNet 0.15.8, mit 3 Aufwärm- und 12 Messdurchläufen pro Fall; die Speicherallokationen wurden mitgemessen.
- Jeder Fall öffnet einen neuen Context, so wie es ein Web-Request tun würde. Inserts und Updates laufen in einer Transaktion, die zurückgerollt wird, damit jeder Durchlauf dieselben Daten sieht.
- Die Maschine: ein Laptop mit Intel Core Ultra 7 255H.
Die sieben Operationen:
| Operation | Wofür sie steht |
|---|---|
| 100 Abfragen nach Id, einzeln | Datensätze einzeln laden, in einer Maske oder einem Import |
| 2.500 offene Bestellungen laden, ohne Tracking | Eine reine Leseliste oder ein Bericht |
| 2.500 offene Bestellungen laden, mit Tracking | Dieselbe Liste, so wie sie der meiste ältere Code lädt |
| Offene Bestellungen auf eine kleine Klasse projizieren | Eine Listenansicht, richtig gemacht |
100 Bestellungen mit Positionen laden (Include) |
Eine Bestelldetailansicht oder ein Export |
| 500 Bestellungen einfügen | Ein Import oder ein Batch-Job |
| 200 Bestellungen laden und ändern | Eine Preisänderung in großem Umfang |
Die Ergebnisse
| Operation | EF6, .NET Framework 4.8 | EF6, .NET 10 | EF Core 10 | EF Core 10 vs. EF6 auf 4.8 |
|---|---|---|---|---|
| 100 Abfragen nach Id | 30,2 ms | 17,1 ms | 8,0 ms | 3,8× schneller |
| 2.500 Bestellungen, ohne Tracking | 58,3 ms | 44,0 ms | 24,4 ms | 2,4× schneller |
| 2.500 Bestellungen, mit Tracking | 87,6 ms | 59,3 ms | 40,5 ms | 2,2× schneller |
| Projektion auf eine kleine Klasse | 9,0 ms | 5,5 ms | 5,8 ms | 1,6× schneller |
| 100 Bestellungen mit Positionen | 17,4 ms | 13,6 ms | 11,1 ms | 1,6× schneller |
| 500 Bestellungen einfügen | 272,0 ms | 127,7 ms | 15,9 ms | 17× schneller |
| 200 Bestellungen ändern | 22,4 ms | 18,7 ms | 6,6 ms | 3,4× schneller |
Beim Speicher ging es in dieselbe Richtung, in manchen Fällen deutlich weiter:
| Operation | EF6, .NET Framework 4.8 | EF Core 10 |
|---|---|---|
| 100 Abfragen nach Id | 7,1 MB | 1,6 MB |
| 2.500 Bestellungen, ohne Tracking | 31,2 MB | 10,8 MB |
| 2.500 Bestellungen, mit Tracking | 37,3 MB | 13,2 MB |
| 500 Bestellungen einfügen | 153,6 MB | 5,4 MB |
| 200 Bestellungen ändern | 8,6 MB | 2,5 MB |
Ein ehrlicher Hinweis: Die Insert-Zahlen für EF6 schwankten zwischen den Läufen, auf .NET Framework etwa zwischen 250 und 330 ms. Die Tabelle zeigt den letzten Lauf. Der Abstand zu EF Core lag in jedem Lauf bei über dem 15-Fachen.
Daten speichern: hier ist der Abstand am größten
Der Insert ist die auffälligste Zahl, und es lohnt sich, sie zu verstehen. Denn genau hier verlieren viele EF6-Anwendungen in Importen und nächtlichen Jobs die meiste Zeit.
for (var i = 0; i < 500; i++)
db.Orders.Add(new Order { CustomerId = 1, Date = DateTime.Today, Total = 10m, Status = "New" });
db.SaveChanges();
Derselbe Code auf beiden Seiten. Zwei Dinge machen EF6 hier langsam:
- EF6 schickt ein
INSERTpro Zeile. 500 Bestellungen sind 500 einzelne Befehle an die Datenbank. EF Core fasst sie zu wenigen gebündelten Befehlen zusammen. Auf einem echten Server im Netzwerk kostet jeder dieser 500 Roundtrips zusätzlich Latenz, der Abstand wird also nur größer. - EF6 prüft bei jedem
Addalle getrackten Entitäten. Jeder Aufruf führt die Änderungserkennung über alles aus, was schon im Context liegt. Viele Entitäten hinzuzufügen wird also umso langsamer, je mehr es sind.
Für den zweiten Punkt gibt es in EF6 selbst eine günstige Lösung, und die sollten Sie kennen, auch wenn Sie nie migrieren:
db.Orders.AddRange(orders);
db.SaveChanges();
Mit AddRange dauerte derselbe Insert in EF6 auf .NET Framework 80,9 ms statt 272 ms und allokierte 21 MB statt 154 MB. Immer noch fünfmal langsamer als EF Core, weil das eine INSERT pro Zeile bleibt, aber ein Faktor 3 für eine geänderte Zeile. Wenn Ihre EF6-Anwendung einen Import hat, der Zeilen in einer Schleife hinzufügt, fangen Sie dort an.
Updates folgen demselben Muster. EF Core schickt die 200 Updates gebündelt, und sein Change Tracker hat pro Entität weniger Arbeit. Das ergibt die 3,4-fache Geschwindigkeit bei einem Drittel des Speichers.
Daten lesen: stetige Gewinne und eine Überraschung
Beim Lesen war EF Core 10 1,6- bis 3,8-mal schneller. Der größte Gewinn lag bei vielen kleinen Abfragen: 100 Abfragen nach Id brauchten mit EF6 30 ms, mit EF Core 8 ms. In EF Core kostet das Übersetzen und Ausführen jeder einzelnen Abfrage deutlich weniger Overhead. Das zählt in Code, der viele kleine Abfragen absetzt, etwa in Masken, die zugehörige Daten Datensatz für Datensatz nachladen.
Die Überraschung war die Projektion. Wenn die Abfrage nur die Spalten auswählt, die eine Maske wirklich braucht, war EF6 auf .NET 10 genauso schnell wie EF Core 10: 5,5 ms gegenüber 5,8 ms. Der Großteil der Arbeit passiert hier in der Datenbank, und beide schicken ihr eine ähnliche SQL-Abfrage. Projektion ist in jeder Version ein guter Rat, wie auch unser Artikel über die fünf häufigsten Fehler, die EF-Core-Abfragen langsam machen gezeigt hat.
EF6 auf .NET 10: ein sinnvoller Zwischenschritt
Viele Teams sehen die Migration als einen großen Sprung: von .NET Framework mit EF6 auf .NET 10 mit EF Core. Das muss nicht so sein. EF6 läuft seit Version 6.4 auf modernem .NET, und in unseren Messungen wurde es allein durch den Wechsel auf .NET 10 1,2- bis 2,1-mal schneller, ohne dass sich an einer Abfrage etwas geändert hat.
Daraus ergibt sich ein Weg in zwei Schritten:
- Schritt 1Anwendung auf .NET 10 umstellen, EF6 behalten
- Schritt 2EF6 Stück für Stück durch EF Core ersetzen
Der erste Schritt bringt Ihnen eine unterstützte Laufzeitumgebung und einen Teil des Geschwindigkeitsgewinns. Der zweite kann dann schrittweise passieren, beginnend mit den Teilen, die am meisten Daten speichern oder die meisten Abfragen ausführen. EF6 auf modernem .NET braucht seine Konfiguration im Code statt in der app.config, das sind ein paar Zeilen:
public class Ef6Configuration : DbConfiguration
{
public Ef6Configuration()
{
SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
SetProviderFactory(SqlProviderServices.ProviderInvariantName, SqlClientFactory.Instance);
}
}
Es gibt Grenzen: Der EF6-Designer funktioniert in modernen .NET-Projekten nicht direkt, Modelle aus EDMX-Dateien brauchen also zusätzliche Arbeit. Und EF6 ist zwar stabil und wird unterstützt, aber nicht mehr aktiv weiterentwickelt. Es ist eine Brücke, kein Ziel. Den vollständigen Umstieg, und was ihn schwerer oder leichter macht, beschreiben wir im Artikel über die Migration von EF6 auf EF Core.
Was das in der Praxis bedeutet
Ein paar Millisekunden pro Abfrage klingen nach wenig. Sie summieren sich aber genau dort, wo ein Unternehmen es tatsächlich spürt:
- Importe und nächtliche Jobs. Bei einem Import von 50.000 Zeilen, der in einer Schleife einfügt, zeigt sich der Faktor 17 darin, dass aus Stunden Minuten werden.
- Viel genutzte Masken. Eine Maske, die pro Aufruf 100 kleine Abfragen ausführt, ist für jeden Benutzer bei jedem Aufruf 22 ms schneller, und der Server hat nur ein Viertel der Allokationsarbeit.
- Serverkosten. Weniger Speicher pro Request bedeutet weniger Garbage Collections und mehr Requests pro Server. Das zählt, wenn Sie in Azure für jede Instanz bezahlen.
In einer Hinsicht sind die Zahlen sogar eine Untergrenze: Unsere Datenbank lief auf derselben Maschine, es gab also kein Netzwerk. Auf einem echten SQL Server oder in Azure SQL kostet jeder Roundtrip, den EF6 macht und EF Core vermeidet, zusätzlich Netzwerklatenz.
Wie viel schneller gewöhnlicher Geschäftscode ganz ohne Datenbank auf .NET 10 läuft als auf .NET Framework, zeigt unsere Messung .NET Framework 4.8 vs. .NET 10.
Fragen, die wir oft hören
Lohnt sich die Migration von EF6 auf EF Core nur wegen der Performance?
Allein wegen der Performance meistens nicht. Aber Performance ist selten der einzige Grund: EF6 bekommt keine neuen Funktionen und bindet viele Anwendungen an .NET Framework. Wenn Sie ohnehin auf .NET 10 umsteigen, gehört die Datenschicht am Ende auf EF Core. Fangen Sie mit den Teilen an, die am meisten Daten speichern.
Können wir EF6 einfach auf .NET 10 laufen lassen?
Ja, seit EF 6.4. Anwendungen, deren Modell im EF6-Designer aus einer EDMX-Datei entstanden ist, brauchen vorher etwas zusätzliche Arbeit. In unseren Messungen war EF6 auf .NET 10 1,2- bis 2,1-mal schneller als auf .NET Framework. Ein guter erster Schritt, aber keine Dauerlösung.
Gelten diese Ergebnisse auch auf einem echten SQL Server?
Die Richtung schon. Die genauen Millisekunden hängen von Ihrem Server, Ihren Daten und Ihren Indizes ab. Bei Inserts und Updates wird der Abstand über ein Netzwerk größer, weil EF6 viel mehr Roundtrips macht. Deshalb lohnt es sich, Ihre eigenen langsamsten Operationen vorher und nachher zu messen.
Unser EF6-Import ist langsam. Was bringt am schnellsten etwas?
Ersetzen Sie Add in einer Schleife durch ein einziges AddRange, oder schalten Sie die automatische Änderungserkennung während des Imports ab. In unserem Test hat das allein den EF6-Insert dreimal schneller gemacht.
Wie wir arbeiten
Genau das ist unsere Arbeit. ProCoding ist ein .NET-Studio in Split, Kroatien, und wir bringen .NET-Framework-Anwendungen samt ihrer EF6-Datenschicht auf .NET 10 und EF Core: erst messen, dann in Schritten migrieren, die das Geschäft nicht bemerkt, und jeden Schritt mit Zahlen überprüfen. In Ihrem eigenen Projekt, gemeinsam mit Ihrem Team oder mit einem .NET-Freelancer in Ihrem Team. Der erste Schritt ist ein kostenloses 30-minütiges Gespräch.
Quellen
- BenchmarkDotNet, das Benchmark-Werkzeug, das wir verwendet haben
- Portieren von EF6 zu EF Core, Microsoft Learn
- Vergleich von EF Core und EF6, Microsoft Learn
- Effiziente Aktualisierung, Microsoft Learn
- Unsere eigenen Messungen, Oktober 2026, wie oben beschrieben
Dieser Artikel dient nur der allgemeinen Information und ist keine Rechts-, Steuer-, Finanz- oder sonstige Fachberatung. Szenarien, Beispiele und Berechnungen dienen der Veranschaulichung. Nutzungsbedingungen und Haftungsausschluss.