Von .NET 8 auf .NET 11 migrieren: die vollständige Checkliste
Eine versionsfixierte Migrations-Checkliste von .NET 8 LTS auf .NET 11 LTS, die SDK-Installation, csproj-Target-Framework, Breaking Changes in ASP.NET Core, EF Core, System.Text.Json und den Überladungsauflösungs-Wechsel in C# 14 abdeckt, inklusive Rollback-Hinweisen.
Zwei LTS-Zyklen auf einmal zu überspringen ist das günstigste .NET-Upgrade, das die meisten Teams in diesem Jahrzehnt vornehmen werden. .NET 8 verlässt den Standard-Support im November 2026, .NET 11 ist das aktuelle LTS, und der Weg dazwischen führt durch drei Sätze von Breaking Changes (.NET 9, 10, 11) sowie drei C#-Sprachversionen (C# 13, 14, mit C# 12 bereits in 8 ausgeliefert). Ein Wochenende konzentrierter Arbeit reicht in der Regel für einen kleinen Service. Ein mittelgroßer Monolith mit EF Core, eigener Middleware und ein paar Source Generators kostet meist drei bis fünf Tage. Codebasen, die BinaryFormatter pinnen, sich auf System.Web.HttpContext-Shims stützen oder In-Process-Azure-Functions betreiben, kosten mehr, und dieser Schmerz tritt zuerst auf.
Dieser Beitrag verwendet net8.0 als Quelle und net11.0 als Ziel. Jeder Codeblock fixiert Versionen explizit, damit die Schritte auch nach einigen Patch-Releases reproduzierbar bleiben.
Warum jetzt migrieren
- Der Standard-Support für .NET 8 endet am 2026-11-10. Danach gibt es weder Sicherheitspatches noch Servicing. Produktionscode auf 8 wird drei Wochen vor Black Friday audit-exponiert.
- .NET 11 liefert relevante Runtime-Gewinne kostenlos: Dynamic PGO ist standardmäßig aktiv, der neue tiered JIT behandelt
async-Zustandsmaschinen ohne die historische Strafe, und Native AOT unterstützt jetzt ASP.NET Core minimal APIs sowie den Großteil des Lesepfads von EF Core. - Der in .NET 9 eingeführte
System.Threading.Lock-Typ beseitigt eine Klasse von Monitor-Reentrancy-Fußangeln. Die Migration zu überspringen lässt das altelock(object)-Muster im Spiel. - C# 14 bringt das stabile
field-Keyword in Properties undpartial-Konstruktoren. Nützlich, aber nicht der Grund für die Migration; behandeln Sie sie als Bonus.
Was bricht
| Bereich | Änderung | Schwere |
|---|---|---|
lock(object) | Der neue System.Threading.Lock-Typ verändert die Monitor-Semantik bei Übernahme | niedrig |
BinaryFormatter | In .NET 9 vollständig entfernt. Kein Opt-in-Switch | hoch |
System.Text.Json | Standard-JsonNumberHandling für JsonObject-Roundtrips änderte sich in .NET 10 | mittel |
| EF Core Query-Pipeline | Übersetzung von Primitive Collections änderte sich in EF Core 10; manches LINQ wirft jetzt | hoch |
| ASP.NET Core Middleware | Überladungs-Signaturen von UseExceptionHandler verschoben sich in .NET 10 | niedrig |
| Native AOT Trim-Warnungen | Mehrere System.Reflection.Emit-Pfade emittieren jetzt neu IL2026-Warnungen | mittel |
| C# 14 Überladungsauflösung | Span-Überladungen schlagen jetzt Array-Überladungen in mehrdeutigen Fällen | mittel |
IWebHostBuilder | Bereits in 8 veraltet, in 11 entfernt. Wechseln Sie zu WebApplication.CreateBuilder | hoch |
dotnet ef-Tool | Major-Versionssprung erforderlich (dotnet tool update --global dotnet-ef --version 11.*) | niedrig |
| Azure Functions | Das In-Process-Modell wurde entfernt; Isolated Worker ist obligatorisch | hoch |
Die vollständige offizielle Liste steht in der .NET 11 Breaking-Changes-Dokumentation. Lesen Sie sie von Anfang bis Ende, bevor Sie eine .csproj anfassen.
Pre-Flight-Checkliste
Führen Sie das aus, bevor Sie irgendein Target Framework ändern.
- Installieren Sie das .NET 11 SDK auf jeder Entwicklermaschine und jedem CI-Runner. Verifizieren Sie mit
dotnet --list-sdksund bestätigen Sie, dass11.0.xerscheint. Das SDK ist Side-by-Side, daher funktioniert .NET 8 weiter. - Fixieren Sie das SDK in
global.json, damit CI nicht stillschweigend vorwärts rollt:// global.json, repo root { "sdk": { "version": "11.0.100", "rollForward": "latestFeature" } } - Erstellen Sie eine Baseline: Führen Sie
dotnet testauf .NET 8 aus und speichern Sie die Ergebnisse. Sie wollen ein sauberes Grün vor dem Start, damit das erste Rot nach dem Upgrade eindeutig ist. - Erstellen Sie einen Snapshot der Produktions-Runtime: dumpen Sie
dotnet --infovon einem Live-Host. Wenn etwas gegen eine Runtime älter als 8.0.0 linkt (eine alte Self-Contained-Publish, ein Drittanbieter-Plugin), finden Sie es jetzt. - Inventarisieren Sie NuGet-Pakete mit
dotnet list package --outdated --include-transitive. Alles, wasMicrosoft.*auf8.0.xpinnt, braucht einen Major-Bump; alles, was auf7.*oder älter gepinnt ist, ist eine rote Flagge. - Verzweigen Sie die Migration. Ein PR pro logischem Schritt ist leichter zurückzunehmen als ein einzelner Green-Light-Riesen-PR.
Migrationsschritte
-
Erhöhen Sie das Target Framework. Öffnen Sie jede
.csprojund ändern Sie den Wert vonTargetFramework(oderTargetFrameworks). Verifizieren Sie mitdotnet buildund behandeln Sie die erste Runde Compile-Fehler als den wahren Umfang der Migration.<!-- src/MyApi.csproj, .NET 11 --> <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net11.0</TargetFramework> <LangVersion>14.0</LangVersion> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> </Project>Verifikation:
dotnet buildbeendet mit 0 zumindest für die Leaf-Projekte, oder schlägt mit Fehlern fehl, die Sie erkennen. -
Aktualisieren Sie alle
Microsoft.*NuGet-Pakete auf die 11.x-Linie. Tun Sie das als einen Batch mitdotnet add packagepro Projekt, statt blindDirectory.Packages.propsanzufassen. Runtime, ASP.NET Core, EF Core und dieMicrosoft.Extensions.*-Pakete versionieren im Gleichschritt mit dem SDK.# .NET 11 dotnet add package Microsoft.AspNetCore.OpenApi --version 11.0.0 dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 11.0.0 dotnet add package Microsoft.Extensions.Hosting --version 11.0.0Verifikation:
dotnet restoreist erfolgreich unddotnet list packagezeigt kein8.0.xmehr unter demMicrosoft.*-Namespace. -
Entfernen Sie die Nutzung von
BinaryFormatter. Wenn die Codebasis etwas mitBinaryFormatterserialisiert, ersetzen Sie es jetzt.System.Text.Json, MessagePack oderprotobuf-netsind die üblichen Ersetzungen, je nachdem ob Sie ein JSON- oder Binärformat brauchen. In .NET 9 oder später gibt es kein Kompatibilitäts-Flag; der Typ ist weg.Verifikation:
grep -r "BinaryFormatter" src/liefert nichts. Wenn Sie alteBinaryFormatter-Blobs aus dem Speicher lesen müssen, schreiben Sie ein einmaliges .NET 8 Migrationstool, um sie zu konvertieren, bevor Sie die .NET 8 Umgebung abschalten. -
Ersetzen Sie
IWebHostBuilderdurchWebApplication.CreateBuilder. Der alte Generic-Host-Shim wurde in .NET 6 deprecated und in .NET 11 entfernt. JedeProgram.cs, die nochHost.CreateDefaultBuilder().ConfigureWebHostDefaults(...)aufruft, kompiliert nicht.// Program.cs, .NET 11, C# 14 var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); builder.Services.AddDbContext<AppDb>(o => o.UseSqlServer(builder.Configuration.GetConnectionString("Default"))); var app = builder.Build(); app.MapOpenApi(); app.MapControllers(); app.Run();Verifikation: die App startet unter
dotnet runund der Endpoint/openapi/v1.jsonantwortet mit HTTP 200. -
Auditieren Sie
System.Text.Jsonauf Verhaltensänderungen. Die Standardbehandlung von Zahlen-Roundtrips inJsonObjectänderte sich in .NET 10 so, dass Integer beim Re-Serialisieren keine Präzision mehr verlieren, und der polymorphe Deserializer ist standardmäßig strenger mit unbekannten Diskriminatoren. Wenn Sie einen öffentlichen API-Vertrag pflegen, lassen Sie Ihre Vertragstests laufen und lesen Sie die Fehler sorgfältig. Der Vertrag ist oft nicht geändert, aber ein vorher stiller Mismatch wirft jetzt eine Exception. Der Begleitbeitrag zum Fix von “JSON value could not be converted to System.DateTime” deckt den häufigsten Konvertierungs-Fehlermodus ab.Verifikation:
dotnet testbeendet sauber für jedes Projekt, das Serialisierung gegen Fixture-JSON-Dateien übt. -
Migrieren Sie EF Core Queries, die Primitive Collections nutzen. EF Core 10 hat überarbeitet, wie
List<int>.Contains(x)übersetzt wird, sodass parametrisierte Collections einen einzelnen SQL-Parameter erzeugen statt in eineIN-Klausel zu expandieren. Das behob das Plan-Cache-Bloat-Problem, brach aber eine kleine Menge von Queries, dieContainsmit anderen serverseitig ausgewerteten Ausdrücken kombinierten. Lassen Sie alle EF Core Integrationstests neu laufen und inspizieren Sie jede Query, die jetztInvalidOperationException: The LINQ expression ... could not be translatedwirft. Die Notluke ist, die Collection mit.ToList()vor dem Join zu materialisieren.Verifikation: jeder Integrationstest, der rohes LINQ über
DbSet<T>übt, besteht; prüfen Sie stichprobenartig das generierte SQL mitLogTo(Console.WriteLine, LogLevel.Information)auf einer repräsentativen Query. -
Übernehmen Sie
System.Threading.Lockselektiv, nicht als Pauschalersatz.private readonly object _gate = new();durchprivate readonly System.Threading.Lock _gate = new();zu ersetzen, ist in den meisten Fällen korrekt, ändert aber, ob Reentrancy aus demselben Thread beobachtbar ist. Gehen Sie die Codepfade zuerst durch. Ein tieferer Trade-off-Vergleich findet sich in lock vs Monitor vs SemaphoreSlim vs System.Threading.Lock.Verifikation: das Code-Review deckt explizit jede
lock(...)-Stelle ab, die geändert wurde; keine funktionale Änderung in der Testsuite. -
Lassen Sie die Trim- und AOT-Analyzer erneut laufen. Wenn das Projekt
<PublishAot>true</PublishAot>oder<TrimMode>full</TrimMode>setzt, emittiert .NET 11 neue Warnungen rund umSystem.Reflection.Emit-Pfade, die unter .NET 8 stumm waren. Die Behebung besteht meist darin,[DynamicallyAccessedMembers]-Annotationen hinzuzufügen oder einen JSON Source Generator zu registrieren. Der Native AOT vs ReadyToRun vs JIT Vergleich behandelt, wann sich welches Modell rechnet.Verifikation:
dotnet publish -c Releaseemittiert nullIL2026- oderIL3050-Warnungen am Leaf-Projekt; das resultierende Native-Binary startet lokal. -
Beheben Sie Überraschungen der C# 14 Überladungsauflösung. C# 14 änderte die Auflösungsregeln so, dass Überladungen, die
ReadOnlySpan<T>akzeptieren, gegenüber solchen mitT[]bevorzugt werden, wenn beide passen. Der meiste Code ist unbetroffen. Die brechenden Fälle sind in der Regel Mocks, Fluent-Assertion-Bibliotheken und eigene Extension Methods, die unter der Annahme geschrieben wurden, dass die Array-Überladung gewinnt. Der Compiler emittiert einen klaren Diagnose; der Fix ist meist ein Cast. Der C# 14 Überladungsauflösungs-Breaking-Change mit Spans führt durch die Diagnose und das Cast-Muster.Verifikation:
dotnet buildist warnungsfrei bei<TreatWarningsAsErrors>true</TreatWarningsAsErrors>. -
Aktualisieren Sie die CI-Runner-Images. Heben Sie die
dotnet-versionvonactions/setup-dotnetin GitHub Actions auf11.0.xan, aktualisieren Sie jedes Dockerfile-Base-Image aufmcr.microsoft.com/dotnet/sdk:11.0undmcr.microsoft.com/dotnet/aspnet:11.0, und entfernen Sie Pins auf das .NET 8 SDK-Image. Self-Hosted-Runner brauchen das SDK manuell installiert, bevor CI grün wird.Verifikation: ein Pipeline-Lauf auf einem Feature-Branch ist von Anfang bis Ende grün, einschließlich des Publish-Schritts.
Verifikation (Smoke-Checkliste)
Nach den obigen Schritten sollte die App jede Zeile dieser Liste bestehen, bevor der Migrations-PR gemerged wird:
dotnet --list-sdkszeigt 11.0.x als die tatsächlich vom Build genutzte Version (dotnet --versionim Repo-Root druckt11.0.x).dotnet restore && dotnet build -c Releasebeendet mit 0 und null Warnungen.dotnet test -c Releaseist grün und die Testanzahl entspricht der .NET 8 Baseline.dotnet publish -c Releaseproduziert ein Artefakt, das lokal startet und/healthserviert.- Ein repräsentativer Lesepfad und ein repräsentativer Schreibpfad werden gegen eine Staging-Umgebung geübt; die Latenz p50/p95 liegt innerhalb von 10 Prozent der .NET 8 Baseline.
- Die Logs zeigen keine First-Chance-Referenzen auf
BinaryFormatter,IWebHostBuilderoderIL2026.
Wenn irgendetwas davon fehlschlägt, halten Sie an. Mergen Sie keine teilweise migrierte Codebasis.
Rollback
Diese Migration ist umkehrbar bis zum ersten Produktions-Deploy, der einen Schreibvorgang unter .NET 11 entgegennimmt. Bis dahin nehmen Sie global.json, TargetFramework und die NuGet-Bumps in einem Commit zurück. Nach dem ersten .NET 11 Produktions-Schreibvorgang ist Rollback technisch möglich, aber selten lohnend: Schemaänderungen, die Sie unter dem EF Core 11 Translator gemacht haben, JSON-Ausgaben, die unter den neuen Standards serialisiert wurden, und jede Übernahme von System.Threading.Lock benötigen separates Nachdenken. Planen Sie vorwärts zu fixen.
Stolperfallen, auf die wir gestoßen sind
- Ein NuGet-Paket, das nur
net8.0zielt, ist nicht zwangsläufig auf net11.0 kaputt, aber es lädt stillschweigend die .NET Standard 2.0 Facade, wenn das Paket eine offenlegt. Das zieht manchmal ältereSystem.*-Abhängigkeiten zurück ins Spiel.dotnet list package --include-transitivenach dem Bump ist nicht optional. Microsoft.Data.SqlClient-Versionen sind wichtig. EF Core 11 willMicrosoft.Data.SqlClient7.x oder neuer. Ein älterer transitiver Pin kompiliert, scheitert dann zur Laufzeit an der TLS 1.3 Aushandlung gegen neuere SQL Server-Instanzen.- Source Generators, die auf Roslyn 4.6 basieren, emittieren Warnungen auf dem Roslyn, der mit .NET 11 ausgeliefert wird. Die meisten lösen sich, indem die
Microsoft.CodeAnalysis.CSharp-Referenz des Generators angehoben wird. Wenn Sie Ihren eigenen Generator versenden, tun Sie das in einem separaten PR. - In-Process Azure Functions sind weg. Wenn ein einzelnes Function-Projekt noch das In-Process-Modell auf .NET 8 verwendet, wird .NET 11 es nicht ausführen. Wechseln Sie zuerst zum Isolated-Worker-Modell, dann bumpen Sie.
- Die Cancellation-Semantik von
HttpClientauf .NET 11 wirft korrekterweiseTaskCanceledException, derenCancellationTokenmit dem gelieferten Token übereinstimmt, während früher einige Pfade mitCancellationToken.Nonewarfen. Catch-Blöcke, die per Pattern-Matching auf den Token zielen, brauchen eine kleine Anpassung; die Begründung steht in der Diskussion zu async void vs async Task in C#.
Verwandt
- ConfigureAwait(false) vs Standard in .NET 11
- Native AOT vs ReadyToRun vs JIT in .NET 11
- EF Core 11 vs Dapper für Bulk Inserts: echter Benchmark
- lock vs Monitor vs SemaphoreSlim vs System.Threading.Lock
- Minimal APIs vs Controller in ASP.NET Core 11
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.