Three architecture mistakes we keep seeing in .NET migrations
After two years auditing .NET migrations from Framework 4.8 to .NET 8+, the same three mistakes show up in roughly 80% of the codebases we review. None are about code quality. All three are about scope.
Updated
Since early 2024 we’ve been part of — or been asked to audit — close to forty
migrations of existing systems from .NET Framework 4.x to modern .NET (6, 7,
or 8 depending on the vintage of the project). The mistakes we see are not
the ones the conference talks warn about. The conference talks warn about
HttpContext.Current, ConfigurationManager.AppSettings, Task.Run
deadlocks, and WCF.
Those are real problems. They are also solvable problems with well-known answers. The mistakes — the decisions that eat months and don’t show up on standups — live one level higher. Here are the three we see most often.
1. Migrating the wrong scope first
The pattern: a team decides to migrate “the API” — or “the services layer”, or “the auth module” — and spends a quarter doing it cleanly. At the end of that quarter, nothing ships, because the migrated component still talks to an un-migrated HTTP client, an un-migrated scheduler, and an un-migrated logging sink, and the deploy topology hasn’t been re-planned.
The underlying error is treating “migrate to .NET 8” as a single, atomic work item. It is not. A migration is at least four separate migrations braided together:
- Language / framework runtime — the actual
TargetFrameworkchange. - Library ecosystem — replacing System.Web, WCF, EF6, Castle Windsor with ASP.NET Core, gRPC / HTTP clients, EF Core, Microsoft DI.
- Hosting / deployment — IIS on Windows Server → Kestrel behind nginx on Linux, or a container / Kubernetes destination.
- Operational plumbing — logging (log4net → Serilog), metrics
(perfmon → OpenTelemetry), config (
web.config→appsettings.json), secrets (Windows Credential Store → Azure Key Vault / Vault / env vars).
Each of the four has its own risk profile and each needs its own rollback plan. Bundling them into a single “go-live” is how migrations stretch from three months to eighteen.
The slice that ships first, reliably, is almost always the opposite of the obvious one. Don’t start with the hottest service — start with the coldest one that touches the most shared infrastructure. That’s where you learn the deployment story, the observability story, and the rollback story without the pressure of a user-facing regression.
2. Rewriting the data access layer before you understand it
The second most common mistake: treating the migration as an opportunity to “finally” move off EF6 to a hand-rolled repository pattern, or the inverse, off Dapper onto “proper” EF Core. These rewrites get framed as technical debt reduction. In practice they almost never pay for themselves within the migration window.
The data access layer in a ten-year-old .NET codebase is the place where
business rules have accumulated as side effects. Eager loads that prevent
race conditions. NOLOCK hints that mask contention. SaveChanges
boundaries that define the effective transaction scope of a use case.
These are not features — they are compensations for something upstream
that nobody remembers. Rewriting the layer without cataloguing them first
means the compensations go with it, and the defects they were masking
resurface in QA or worse, in production.
The reliable pattern: migrate the data access layer shape-preserving
first. Keep EF6 semantics if you had them. Keep the same DbContext
boundaries, the same lazy-loading flags, the same transaction scopes.
Once you’re on modern .NET and the rest of the platform is stable, then
revisit — with logs, with traces, with an audit of what each quirk was
actually compensating for. The two-phase approach takes about 20% more
calendar time and roughly 70% less production incident time.
3. No decision about what “done” means
The third mistake is the most political and also the most damaging. It’s the mistake of treating the migration as a technical project with a technical success criterion — “all services run on .NET 8 in production.”
That criterion is not wrong; it is insufficient. Because the question that actually predicts whether a migration lands is: on the day we call this done, what can the organisation do that it couldn’t do yesterday?
If the answer is “nothing, the system behaves the same” — the migration will be deprioritised the moment a revenue-linked project appears. We’ve watched this happen repeatedly. The migration runs for seven months, gets to 70% complete, and then the team gets pulled onto a billing redesign because the billing redesign has a dashboard metric attached to it.
The antidote is deciding up-front what capability the migration unlocks — something that is impossible on .NET Framework 4.8 and becomes straightforward on .NET 8. The common ones we see land:
- Deploying on Kubernetes (and decommissioning a dedicated Windows Server fleet).
- Streaming responses / SignalR-based real-time dashboards that the business has been asking for.
- Launching on a second region / cloud where Windows Server licensing was previously blocking the deal.
- Cutting cloud spend by a target percentage, documented and measured.
Pick one. Put it in the migration brief. When the first steering committee meeting three months in asks whether the migration is on track, you have something to point at that isn’t percentages of services migrated.
What these three share
All three mistakes have the same shape: they treat migration as a technical task, when it is actually a delivery task that happens to be technical. The code is not the hard part. Sequencing the code — and convincing the rest of the organisation that the sequencing is worth defending — is the hard part.
If you’re planning a migration and any of these ring uncomfortably true, that’s the moment to re-plan, not after the first sprint. We run half-day migration audits for Indonesian enterprise teams, where the output is a sequenced delivery plan, a risk register, and a capability target. Book a free consult and we’ll walk through your situation.
Denny is Technology & Delivery Director at Aegislabs and has led .NET migrations at banks, telcos, and government agencies since 2014. This post is expanded from an internal memo written in March 2026, now shared with minor edits.