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:

<PropertyGroup>
  <PublishAot>true
    </PublishAot>
</PropertyGroup> 
Zudem muss man die generierten Interceptoren in der Projekt­datei so aktivieren:

<PropertyGroup>
  <InterceptorsNamespaces>$(InterceptorsNamespaces);
    Microsoft.EntityFrameworkCore.GeneratedInterceptors
    </InterceptorsNamespaces>
</PropertyGroup> 
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<Todo>("SELECT * FROM Todos");
var todosList = new List<Todo>(); 
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&lt;Todo&gt;("""
  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

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
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Bausteine guter Architektur - Entwurf und Entwicklung wartbarer Softwaresysteme, Teil 2
Code sauberer gestalten anhand von wenigen Patterns und Grundhaltungen.
6 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige