Blazor and .NET

Moving from EF6 to EF Core: what changes, what breaks and how to do it safely

There is no direct upgrade from Entity Framework 6 to EF Core. The order that works, the differences that change your data silently, and how to test them.

Vlado Pandžić

Vlado Pandžić · Founder · Senior .NET architect
Published · 6 min read

The data layer is where a .NET modernisation goes quietly wrong. The screens look the same, the build is green, and a query that used to load an order with its lines now loads the order without them. Microsoft is clear about it: EF Core is a complete rewrite of Entity Framework, and there is no direct upgrade path from EF6.

The good news is that you don’t have to do everything at once.

Do you have to move now?

Not necessarily. EF6 runs on modern .NET too: version 6.5.2, released in April 2026, is compatible with .NET 10. EF Core, on the other hand, runs only on modern .NET, not on .NET Framework. That gives the safe order:

  1. Move the application from .NET Framework to .NET 10 and keep EF6.
  2. Then move from EF6 to EF Core, one area at a time.

Two big changes in one release make it hard to tell what caused a problem. One at a time keeps every step testable. How to approach the first step is covered in migrating from .NET Framework to .NET 10.

Why move to EF Core at all, if EF6 keeps working? Because all new development happens in EF Core, and EF6 gets no new features. In practice that means better performance, bulk updates and deletes without loading data into memory (ExecuteUpdate, ExecuteDelete), and the tooling the rest of .NET is built around. Microsoft cites a customer who cut the cost of a heavy query 40 times after the move, thanks to query splitting.

The order that works

  1. Step 1Inventory of the data layer
  2. Step 2EF6 on .NET 10
  3. Step 3EF Core one area at a time
  4. Step 4Remove EF6

Step 3 is possible because EF6 and EF Core can run side by side in the same application. Orders can move to EF Core while invoicing still runs on EF6, and each area goes to production on its own.

The inventory in step 1 answers the questions that decide the effort:

  • Is the model in an EDMX file from the designer, or in code?
  • Does the code rely on lazy loading?
  • How much raw SQL, Entity SQL and how many stored procedures are there?
  • Which queries are business-critical: invoicing, stock, payroll?
  • Are there integration tests against a real database?

Differences that change behaviour without an error

Most differences show up as compile errors, and those are the easy ones. These compile and still behave differently:

In EF6 In EF Core What to do
Lazy loading works with virtual navigation properties Off, unless you add the Microsoft.EntityFrameworkCore.Proxies package and call UseLazyLoadingProxies() Load related data explicitly with Include, or turn proxies on deliberately
Some C# functions are translated to SQL Not all of them; an untranslatable expression throws at runtime Run every query path in tests, not first in production
Change detection runs over the whole graph, often Per entity, and less often Check code that changes entities and relies on automatic detection
Orphaned child rows are kept Orphaned dependents are deleted Check relationships where children can lose their parent
Data annotations are validated on save No validation on save Validate in the application before saving

Lazy loading is the one to check first. In EF6 this code loads the order lines on first access. In EF Core without proxies, order.Lines is simply not loaded, and the total is wrong without any error:

var order = db.Orders.Single(o => o.Id == id);
// EF6 loads the lines here. EF Core without proxies does not.
var total = order.Lines.Sum(l => l.Amount);

Explicit loading is clearer, and usually faster:

var order = db.Orders
    .Include(o => o.Lines)
    .Single(o => o.Id == id);

What simply isn’t there

  • EDMX. EF Core has no designer and doesn’t read EDMX files. Instead, the model is generated from the existing database and maintained in code from then on:

    dotnet ef dbcontext scaffold "Server=...;Database=Shop;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer
  • Entity SQL and ObjectContext. Queries written in Entity SQL move to LINQ or to raw SQL, and code that reaches ObjectContext through IObjectContextAdapter has to be rewritten.

  • The migrations history. EF Core can’t continue EF6 migrations. Apply the last EF6 migration, create a fresh initial migration in EF Core and continue from there. The database and its data stay as they are.

  • Database initializers and automatic migrations. Schema changes go through explicit migrations instead.

Raw SQL still works, with safer defaults. FromSql turns interpolated values into SQL parameters:

var orders = await db.Orders
    .FromSql($"EXECUTE dbo.GetOpenOrders {customerId}")
    .ToListAsync();

How to test that the data hasn’t changed

“It compiles” proves nothing here, and Microsoft’s own guide says the same. What works:

  1. Compare the SQL. Log the SQL that EF6 and EF Core send for the critical queries, and compare. EF Core shows it with ToQueryString() or LogTo.
  2. Test against a real database, not an in-memory one. Differences in loading and deleting only show up with real tables and relationships.
  3. Compare the results. Totals and counts from invoicing, stock or payroll, before and after, on the same copy of production data.
  4. Release area by area, and watch errors and slow queries in production for the first days.

How long it takes

Rough ranges, once the application already runs on modern .NET:

  • a small model configured in code, with little raw SQL: a few days
  • a typical business application with an EDMX model, lazy loading and stored procedures: 2 to 6 weeks
  • a large system with hundreds of entities, Entity SQL and no tests: 2 to 4 months, area by area, alongside normal development

The mapping itself is rarely the slow part. Finding the behaviour differences, and building the tests that prove they’re handled, is.

Once the data layer runs on EF Core, the most common EF Core performance problems are the next thing worth checking.

How we work

ProCoding is a .NET studio from Split, Croatia, and the data layer is where we spend much of our time: EF Core performance, migrations and taking over systems nobody wants to touch. We start with an inventory of your data layer, propose the order, and carry out the move area by area, with tests that show the data hasn’t changed. On our own, with your team, or as a freelance .NET developer in your team. More on modernising older .NET applications is on our Blazor and migration page. The first step is a free 30-minute call.

Sources

This article is general information only, not legal, tax, financial or other professional advice. Scenarios, examples and calculations are illustrative. Terms of use and disclaimer.

Related articles

© 2026 ProCoding — All rights reserved.Legal notice and privacyTerms of useSplit, Croatia