EF Core: "The Database Is Already Up to Date"

You added a migration. Update-Database runs clean and says there's nothing to apply. The table it was supposed to create still isn't there. EF Core isn't wrong about what it did — it's wrong about what the database already has, and that's a different, more specific bug than "my migration is broken."

reference guide EF Core .NET

// what EF Core is actually trusting

Every EF Core migration you've applied gets a row in a table called __EFMigrationsHistory, right inside your own database. Update-Database doesn't inspect your schema to decide what's missing — it reads that table, compares it against the migrations in your project, and applies whatever isn't listed. If that table says a migration already ran, EF Core believes it, even when the table or column that migration was supposed to create was never actually built.

// cause 1 — EnsureCreated() and Migrate() don't agree on the rules

context.Database.EnsureCreated() builds your entire schema directly from the current model — fast, convenient, common in test setups and early prototypes. What it does not do is write anything to __EFMigrationsHistory, because it isn't using migrations at all; it's a completely separate code path. Call it once — even in a test project, even years ago, even by a different developer — and you get a real, fully-formed schema that EF's migration system has no record of.

what actually happens, step by step
the code, at some earlier point
context.Database.EnsureCreated();  // seeds a working database for local dev/tests
later, the normal migration workflow
Add-Migration AddCustomerEmail
Update-Database
what happens
Msg 2714, Level 16, State 6, Line 1
There is already an object named 'Customers' in the database.
why
__EFMigrationsHistory is empty, so EF Core believes no migration has ever run -- including the very first one, which creates the Customers table. It tries to run that first migration now, against a database where Customers already exists from EnsureCreated(), and SQL Server refuses.
the fix
INSERT INTO __EFMigrationsHistory (MigrationId, ProductVersion)
VALUES ('20260101000000_InitialCreate', '9.0.0');
-- tells EF Core that migration already ran, so it stops trying to recreate Customers
-- and moves straight to applying AddCustomerEmail, the one that's actually missing
The inverse of this guide's title also happens: sometimes __EFMigrationsHistory gets partially or manually seeded, EF Core believes every migration already ran, reports "up to date," and the column you just added in a new migration never gets applied at all — same root mismatch, opposite symptom.

// cause 2 — a migration file with no matching .Designer.cs

Every migration is actually two files: <Timestamp>_MigrationName.cs (the Up()/Down() methods) and <Timestamp>_MigrationName.Designer.cs (a snapshot of the model at that point, which is how EF Core identifies the migration at all). If the .Designer.cs file is missing — deleted by hand, excluded by an overly broad .gitignore pattern, lost in a merge conflict — EF Core doesn't see a broken migration, it doesn't see a migration at all. It's silently skipped, and everything after it in the sequence gets confused about what's already applied.

what this actually looks like in the Migrations folder
Migrations/
├── 20260101000000_InitialCreate.cs
├── 20260101000000_InitialCreate.Designer.cs
├── 20260201000000_AddCustomerEmail.cs
└── (20260201000000_AddCustomerEmail.Designer.cs is missing)  ← this migration gets silently skipped
If a migration is missing its effect but the .cs file is clearly in the project, check the Migrations folder for the matching .Designer.cs before anything else — it's a five-second check that rules out this entire cause.

// the actual fix, not a workaround

Deleting the migrations folder and starting over "fixes" this by hiding it — the next environment with a different history runs into the same mismatch. The real fix is reconciling __EFMigrationsHistory with reality, and the first step is finding out exactly where the two disagree rather than guessing:

see what EF Core thinks is applied vs. pending
dotnet ef migrations list
-- marks each migration [applied] or [pending] based on __EFMigrationsHistory alone --
-- compare that against which tables/columns actually exist to find the exact mismatch

For every migration the list shows as [pending] whose effect is already in the database (the EnsureCreated() case above), insert its row by hand instead of letting EF try to recreate what's already there — using the same pattern shown earlier, with that migration's own ID and a current EF Core version string in place of '20260101000000_InitialCreate' and '9.0.0'.

Once the history table reflects what's genuinely already in the database, Update-Database only applies what's actually missing — which is the entire point of having a history table in the first place.