Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 7 Min.

Stark beschränkt

In Entity Framework Core 9.0 liefert Microsoft erstmals eine Unterstützung für den Native-AOT-Compiler mit, die aber leider noch geringen praktischen Nutzen hat.
© dotnetpro
Die Datenzugriffskolumne in der dotnetpro-Ausgabe 11/2024 thematisierte die Neuerungen in Entity Framework Core 9.0 unter dem Titel „AOT noch in Arbeit“ [1], denn in der Preview-6-Version, die Basis für diesen Beitrag war, gab es noch kein fassbares Ergebnis zur AOT-Kompilierung in Microsofts objektrelationalem Mapper.Bis zur endgültigen Version, die am 12. November 2024 erschienen ist, hat sich noch etwas getan. Dieser Beitrag betrachtet kritisch das Ergebnis.

Native-AOT-Compiler seit .NET 7.0

Den Just-in-Time-Compiler für .NET gibt es seit .NET Framework 1.0; er erzeugt erst zur Laufzeit den Maschinencode aus der Intermediate Language. Danach gab es immer wieder auch Möglichkeiten, direkt zur Entwicklungszeit Maschinencode zu erzeugen: den Native Image Generator für .NET Frame­work (NGen), .NET Native für Universal Windows Apps, IL2CPP (Intermediate Language To C++) für Unity, den AOT-Compiler für die Apple-Betriebssysteme und Android in ­Mono/Xamarin sowie ReadyToRun (R2R) im modernen .NET.Seit .NET 7.0 liefert Microsoft einen neuen Ahead-of-Time-Compiler (AOT), der es erlaubt, .NET-Anwendungen komplett in Maschinencode ohne Just-in-Time-Kompilierung zur Laufzeit auszuliefern. Im Gegensatz zu ReadyToRun (R2R), das nur teilweise vorkompiliert, erzeugt der neue „Native AOT“-Compiler wirklich native, eigenständige Maschinencodes ohne Just-in-Time-Kompilierung. In .NET 7.0 war Native AOT jedoch zunächst nur für Konsolenanwendungen verfügbar.Seit .NET 8.0 sind nun zusätzlich auch folgende Anwendungsarten beim AOT-Compiler möglich: Hintergrunddienste (Worker Services), gRPC-Dienste und Web-APIs. Bei den Web-APIs ist jedoch lediglich das „Minimal Web API“ genannte Modell möglich – mit JSON-Serialisierung via System.Text.Json im Source-Generator-Modus und zunächst ­ohne Metadaten in der OpenAPI Specification.

Neuerungen für Native AOT in .NET 9.0

In .NET 9.0 sind folgende Erweiterungen des AOT-Compilers enthalten:
  • ASP.NET-Core-SignalR-Hubs können mit Native AOT kompiliert werden.
  • .NET MAUI für iOS und macOS: Die zuvor in .NET 8.0 eingeführte experimentelle Unterstützung gilt nun als einsatzklar. Das Trimming wurde verbessert: Bisher gab es nur ein Trimming von Basisklassen sowie Android- und iOS-APIs. Nun gibt es auch ein Trimming des eigenen Codes der Entwickler:innen sowie von Drittanbieterbibliotheken.
  • OpenAPI Specification (OAS) in ASP.NET Core Minimal Web APIs.
  • AOT für WinUI-3-Oberflächen seit Windows-App-SDK 1.6.
  • [FeatureSwitchDefinition] und [FeatureGuardAttribute] für das Entfernen von Codes bei Trimming/AOT.

Vor- und Nachteile des AOT-Compilers

Die Native-AOT-Kompilierung ermöglicht einen deutlich schnelleren Anwendungsstart und kleinere Deployment-Pakete. Auf der Negativseite ist jedoch eine leicht schlechtere Performance bei der Ausführungszeit zu verzeichnen, weil es keinen Just-in-Time-Compiler mehr gibt, der auf dem Zielsystem für dieses optimieren kann.Schwerer wiegt aber bei den Nachteilen, dass einige Funktionen nicht mehr verfügbar sind, die Laufzeitkompilierung voraussetzen. Dazu gehören:
  • Laufzeitcodegenerierung mit Reflection Emit,
  • dynamisches Nachladen von Assemblies für Add-ins/Plug-ins,
  • Component Object Model (COM),
  • Windows-Runtime-APIs (WinRT),
  • Windows Management Instrumentation (WMI),
  • Zugriff auf Active Directory Services,
  • C++ / CLI,
  • AOT mit Web-APIs in den Internet Information Services (IIS),
  • die meisten anderen OR-Mapper wie nHibernate und Peta­Poco,
  • JSON-Serialisierung mit JSON.NET (Newtonsoft JSON),
  • AutoMapper und viele andere Drittanbieterkomponenten, die eine der oben genannten Funktionen nutzen.
Microsoft sieht für die Zukunft von .NET eine Koexistenz ­zwischen dem alteingesessenen Just-in-Time-Compiler (JIT) und dem neuen Ahead-of-Timer-Compiler (AOT) vor.

Native AOT mit Entity Framework Core

Microsoft hatte Native-AOT-Unterstützung für Entity Framework Core Version 8.0 schon auf dem Plan [2]. Aber erst in Entity Framework Core 9.0 gibt es einen ersten, doch leider minimalen Fortschritt, siehe [3]. Vieles ist allerdings weiterhin offen, siehe [4]. In der Dokumentation unter [5] spricht Microsoft daher eine klare Warnung gegen den Produktionseinsatz von Entity Framework Core 9.0 mit dem Native-AOT-Compiler aus (Bild 1).
Aussage von Microsoft zur Native-AOT-Kompilierung in Entity Framework Core 9.0 (Bild 1) © Autor

AOT-Herausforderungen bei Entity Framework Core

Damit Entity Framework Core mit dem Native-AOT-Compiler funktionieren kann, muss die in Entity Framework Core verwendete Laufzeitkompilierung an zwei Stellen durch Codegenerierung zur Entwicklungszeit ersetzt werden:
  • die Modellkompilierung bei der ersten Verwendung der Kontextklasse in einem Prozess,
  • die LINQ- beziehungsweise SQL-Abfragekompilierung beim ersten Aufruf einer Abfrage.
Die manuelle Modellkompilierung gibt es in Entity Framework Core seit Version 7.0. Seit Version 9.0 steht die automatische Modellkompilierung zur Verfügung, siehe [1].

Abfragekompilierung zur Entwicklungszeit

Für die LINQ- oder SQL-Abfragekompilierung bietet Microsoft in Entity Framework Core 9.0 leider nur einen kleinen ersten Schritt. Die Abfragekompilierung für die Native-AOT-Unterstützung in Entity Framework Core 9.0 basiert auf statischer Codeanalyse, bei der LINQ-Befehle im Programmcode gefunden werden. Für diese Abfragen wird dann zur Entwicklungszeit Programmcode generiert und beim Kompilieren im Programmcode via C#-Interceptoren eingesetzt.Entwicklungszeitkompilierung ist durch die statische Code­analyse bisher leider nur möglich für statische LINQ-Abfragen in Methodensyntax, die sofort mit ToList(), ToArray() und ToHashSet() materialisiert werden:

var blogs = ctx.Websites.Include(b => b.News).Where(
  x => x.Id > 42).ToList(); 
Sämtliche in der Tabelle 1 genannten Abfragen lassen sich hingegen leider nicht zur Entwicklungszeit kompilieren.

Tabelle 1: In diesen Fällen versagt Entity Framework Core 9.0 bei der Native-AOT-Kompilierung

Abfrage Fehler
Materialisierung einzelner Objekte mit FirstOrDefault() und SingleOrDefault() Kompilierung schlägt fehl mit QueryPrecompilationError.
Materialisierung einzelner Objekte mit Find() Kompilierung wird erst gar nicht versucht. Es kommt zum Laufzeitfehler: „Query wasn’t precompiled and dynamic code isn’t supported (NativeAOT).“
LINQ-Abfragen in LINQ-Syntax, zum Beispiel:  from x in ctx.Websites.Include(b => b.Websites) where x.Id > 42  select x Kompilierung wird erst gar nicht versucht. Es kommt zum Laufzeitfehler: „Query wasn’t precompiled and dynamic code isn’t supported (NativeAOT).“
Alle SQL-Abfragen Kompilierung schlägt fehl mit QueryPrecompilationError.
LINQ-Abfragen in Methodensyntax, die schrittweise zusammengesetzt werden, zum Beispiel:  IQueryable q = ctx.Websites.Include(b => b. Websites)if (filter) q = q.Where(x => x.Id > 42)
Kompilierung wird erst gar nicht versucht. Es kommt zum Laufzeitfehler: „Query wasn’t precompiled and dynamic code isn’t supported (NativeAOT).“
Globale Query-Filter, zum Beispiel: modelBuilder.Entity() .HasQueryFilter(b => b.News.Count > 42) Kompilierfehler: „Compiled model can’t be generated, because query filters are not supported.“
Datenbank anlegen zur Laufzeit mit ctx.Database.EnsureCreated() Laufzeitfehler: „Design-time DbContext operations are not supported when publishing with NativeAOT.“

Aktivierung der Native-AOT-Kompilierung

Zur Aktivierung der Native-AOT-Kompilierung verwendet man, wie beim Native-AOT-Compiler üblich, folgende Projekteinstellung:


  true
    
 
Zudem muss man die generierten Interceptoren in der Projekt­datei so aktivieren:


  $(InterceptorsNamespaces);
    Microsoft.EntityFrameworkCore.GeneratedInterceptors
    
 
Nun geht es daran, das Model und die Abfragen zu kompilieren:

dotnet ef dbcontext optimize --precompile-queries 
  --nativeaot 
Die Ausgabe ist in Bild 2 gezeigt.
Ausgabe bei der Abfragekompilierung zur Entwicklungszeit (Bild 2) © Autor
Unter der Voraussetzung, dass die Kontextklasse und alle Abfragen im gleichen Projekt liegen, entstehen dort zwei Ordner mit generiertem Code (siehe Bild 3):
Generierte Dateien, unter der Voraussetzung, dass Kontextklasse und alle Abfragen in verschiedenen Projekten liegen (Bild 3) © Autor
  • CompiledModels: das kompilierte Modell mit viel C#-Code;
  • Generated: die kompilierten Abfragen mit SQL und viel
    C#-Code.
In der Praxis werden Kontextklasse und Abfragen in verschiedenen Projekten liegen. Die Dokumentation geht allerdings auf diesen Praxisfall gar nicht ein. Tests haben ergeben, dass man in diesem Fall in dem Projekt mit der Kontextklasse Folgendes auszuführen hat:

dotnet ef dbcontext optimize --precompile-queries 
  -–nativeaot 
In dem Projekt mit den Abfragen gilt es, im Code einen Verweis auf die Kontextklasse zu ergänzen:

[assembly: DbContext(typeof(Context))] 
Danach führt man folgenden Kommandozeilenbefehl in dem Projekt mit den Abfragen aus:

dotnet ef dbcontext optimize --precompile-queries 
  --nativeaot -c * 
Diese Kommandozeilenbefehle müssen nach jeder Änderung am Modell oder den Abfragen immer wieder ausgeführt werden. Die zuvor schon generierten Ordner CompiledModels und Generated sollte man vorher löschen.Beim Veröffentlichen der Anwendung mit dotnet publish sieht man dann Warnungen wie „EF Core isn’t fully compatible with NativeAOT, and running the application may generate unexpected runtime failures.“

Aktivierung von Precompiled Queries ohne Native AOT

Precompiled Queries kann man auch ohne Native AOT einsetzen, indem man zur Entwicklungszeit folgenden Kommandozeilenbefehl ausführt:

dotnet ef dbcontext optimize --precompile-queries 
Dies kann die Laufzeitgeschwindigkeit einer Anwendung erhöhen. Es gelten aber die gleichen Einschränkungen, die weiter oben genannt wurden. Daher ist auch dieses Feature zum Stand Entity Framework Core 9.0 wenig praxistauglich!

Fazit

Die derzeitige Implementierung der vorkompilierten Abfragen in Entity Framework Core 9.0 ist in den meisten Praxis­anwendungen noch nicht zu gebrauchen, und damit kann man Microsofts objektrelationalen Mapper weiterhin nicht in Native-AOT-kompilierten Anwendungen nutzen. Will man Entity Framework Core einsetzen, muss man also auf die Native-AOT-Kompilierung verzichten oder Alternativen verwenden (siehe unten). Es gibt aber berechtigte Hoffnung, dass Microsoft in Entity Framework Core 10.0, das im November 2025 erscheinen soll, an dieser Stelle etwas nachlegt.

Alternativen zu Entity Framework Core bei Native AOT

Da Entity Framework Core 9.0 in Verbindung mit dem Native-AOT-Compiler also kaum brauchbar ist, müssen Sie, wenn Sie den Native-AOT-Compiler verwenden wollen, die Datenbankzugriffe mit einer der folgenden Techniken realisieren:
  • DataReader, DataSet und Command-Objekte aus ADO.NET,
  • DapperAOT (eine funktionsreduzierte Variante von Dapper) [6],
  • Nanorm [7].
Nanorm funktioniert ähnlich wie Dapper von StackExchange [8], das heißt, man verwendet für die Abfragen SQL und nicht LINQ wie bei Entity Framework Core, zum Beispiel:

using var db = new SqliteConnection(connectionString); 
var todos = db.QueryAsync("SELECT * FROM Todos");
var todosList = new List(); 
Dabei ist QueryAsync<T> eine Erweiterungsmethode auf der Klasse Microsoft.Data.Sqlite.SqliteConnection.Auch zum Einfügen, Ändern und Löschen von Datensätzen verwendet man die SQL-Syntax in einer Zeichenkette:

var todo = new Todo { Title = title };
var createdTodo = await db.QuerySingleAsync("""
  INSERT INTO Todos(Title, IsComplete)
  Values(@Title, @IsComplete)
  RETURNING *
  """,
  todo.Title.AsDbParameter(),
  todo.IsComplete.AsDbParameter()); 
Damit bietet Nanorm deutlich weniger Datenbankmanagementsystem-Abstraktion, Entwicklungskomfort und Robustheit als Entity Framework Core.

Fussnoten

  1. Holger Schwichtenberg, AOT noch in Arbeit, dotnetpro 11/2024, Seite 108 ff., http://www.dotnetpro.de/A2411DataAccess
  2. Microsoft Learn, Plan for Entity Framework Core 8, http://www.dotnetpro.de/SL2506-07DataAccess1
  3. dotnet/efcore auf GitHub, Issue #29754, [9.0] NativeAOT work, http://www.dotnetpro.de/SL2506-07DataAccess2
  4. dotnet/efcore auf GitHub, Issue #34446, Future NativeAOT work, http://www.dotnetpro.de/SL2506-07DataAccess3
  5. Microsoft Learn, What‘s New in EF Core 9, http://www.dotnetpro.de/SL2506-07DataAccess4
  6. DapperAOT auf GitHub, http://www.dotnetpro.de/SL2506-07DataAccess5
  7. Nanorm auf GitHub, http://www.dotnetpro.de/SL2506-07DataAccess6
  8. Dapper auf GitHub, http://www.dotnetpro.de/SL2506-07DataAccess7

Neueste Beiträge

Spotlight #1: Azure IoT Operations, Video Teil 2/3 - DWX Spotlight
Das erste DWX Spotlight mit Special Guest Florian Bader. Im Teil 2 des Videos erklärt Florian unter anderem, wie es mit der Sicherheit bei Industrieanlagen und Messdatenerfassung aussieht.
2 Minuten
10. Jul 2025
KI lässt Entwickler ihre Leidenschaft zum Programmieren neu entdecken - Motivation
Softwareentwicklung ist gleich Spaßfreie Zone? Das muss nicht sein: Der Beitrag beleuchtet, wie Teams ihren Kopf wieder freibekommen und ihre Freude am Entwickeln neu entdecken.
5 Minuten
10. Jul 2025
Contacts guaranteed: Die Partnerunternehmen auf der DWX 2025 - Konferenz
Über 30 führende Unternehmen, unzählige Impulse: Auf der DWX 2025 in Mannheim zeigten unsere Partner, was Tech heute kann – und morgen möglich macht.
2 Minuten
10. Jul 2025
Miscellaneous

Das könnte Dich auch interessieren

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
Dateikompression mit paralleler Verarbeitung
Ein Tool zur parallelen Dateikompression in Go kombiniert Geschwindigkeit und Speicherverwaltung durch ein intelligentes Semaphore-System.
2 Minuten
23. Jun 2025
ZLinq: LINQ-Bibliothek ohne Speicherallokation
Die neu veröffentlichte ZLinq von Yoshifumi Kawai verspricht eine effiziente LINQ-Bibliothek ohne Speicherallokationen.
3 Minuten
17. Jun 2025
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige