Fixing EF Core Pending Model Changes Errors
After upgrading to EF Core 9, the migration step that has worked for years fails immediately: System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Migrations.PendingModelChangesWarning': The model for context 'ShopContext' has pending changes. Add a new migration before updating the database. Nobody changed the model on purpose. Running dotnet ef migrations add Check produces a migration that updates seed rows with a new timestamp, or re-creates an index with a different filter string, or does nothing visible at all. EF Core 9 started treating any difference between the current model and the latest migration snapshot as an error when applying migrations. That is a useful guardrail β it catches model changes shipped without their migration β but it also exposes long-hidden non-determinism. This guide shows how to find what EF thinks changed, fix the genuine and the spurious cases, and turn the check into a CI gate. It belongs to Entity Framework Core Migrations.
Symptom / Error Signatures
The EF Core 9 error, raised from Migrate(), MigrateAsync(), dotnet ef database update or a migration bundle:
System.InvalidOperationException: An error was generated for warning
'Microsoft.EntityFrameworkCore.Migrations.PendingModelChangesWarning':
The model for context 'ShopContext' has pending changes. Add a new migration before updating the database.
This exception can be suppressed or logged by passing event ID 'RelationalEventId.PendingModelChangesWarning'
to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
Related symptoms: dotnet ef migrations has-pending-model-changes (EF Core 8+) exits non-zero; every new migration you scaffold contains the same unexplained UpdateData calls; or scaffolding an empty migration produces operations touching seed data or index filters that nobody edited.
Root Cause Analysis
The snapshot file records the model as of the latest migration. When you apply migrations, EF Core 9 builds the current model from code and compares it with that snapshot; any difference means the code expects a schema that no migration creates. There are three families of cause:
| Cause | Example | Correct fix |
|---|---|---|
| genuine model change without a migration | new property, changed max length, new index | add the migration |
| non-deterministic model configuration | HasData(new { CreatedAt = DateTime.Now }), Guid.NewGuid() in seed data |
make values constant |
| environment-dependent configuration | model built differently per environment or culture | make OnModelCreating deterministic |
| EF or provider upgrade changing annotations | new default annotations after upgrading | add a (usually no-op) migration to refresh the snapshot |
Non-deterministic seed data is the classic hidden case. HasData values become part of the model; if one is computed at runtime, every build produces a different model, the snapshot never matches, and each new migration contains UpdateData statements that rewrite seed rows. Before EF Core 9 this was merely noisy; now it blocks deploys.
Suppressing the warning is possible but defeats the point: the check exists to stop a release from running against a schema that lacks what its model expects β the precise situation that causes runtime failures such as Invalid column name after a deploy.
Immediate Mitigation
1. See what EF thinks changed. Scaffold a throwaway migration and read it; remove it afterwards if it is not the fix.
# Shell Β· developer workstation Β· touches no database
dotnet ef migrations has-pending-model-changes --project src/Shop.Data --startup-project src/Shop.Api
dotnet ef migrations add PendingCheck --project src/Shop.Data --startup-project src/Shop.Api
cat src/Shop.Data/Migrations/*_PendingCheck.cs
dotnet ef migrations remove --project src/Shop.Data --startup-project src/Shop.Api # if it is not the fix
2. If it is a genuine change, keep the migration. Review it like any other β additive, backward compatible, online where needed β and ship it. The deploy that failed was protecting you from running code that expects a column that does not exist.
3. If it is non-deterministic seed data, make the values constant.
// C# Β· Data/Configurations/OrderStatusConfiguration.cs Β· seed data must be deterministic
// WARNING: changing seed values later generates UpdateData migrations β that is expected and reviewable.
builder.HasData(
new OrderStatus { Id = 1, Name = "Pending", CreatedAt = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) },
new OrderStatus { Id = 2, Name = "Paid", CreatedAt = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc) });
// before: CreatedAt = DateTime.UtcNow β a different model on every build
Then scaffold one migration that brings the snapshot in line with the now-constant values, and confirm a second scaffold is empty.
4. Do not suppress the warning in production configuration. If an urgent deploy is blocked by upgrade-only annotation noise, the right fix is still to commit a refresh migration, which takes minutes. Suppression (ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))) hides genuine missing migrations too.
Permanent Fix / Long-Term Pattern
Run the same check in CI that EF Core 9 runs at deploy time, so it fails in the pull request instead of in the pipeline. dotnet ef migrations has-pending-model-changes exits non-zero when the model and snapshot differ; add it to every build.
# YAML Β· CI job step Β· .NET SDK with dotnet-ef restored Β· no database connection needed
- name: Fail if the model has changes without a migration
run: |
dotnet tool restore
dotnet ef migrations has-pending-model-changes \
--project src/Shop.Data --startup-project src/Shop.Api
Keep OnModelCreating deterministic: no DateTime.Now, no random values, no configuration that depends on environment variables or the machineβs culture. Treat the model snapshot file as a reviewed artefact β changes to it should correspond to a migration in the same pull request β and resolve snapshot merge conflicts by regenerating rather than hand-merging, the same principle as resolving schema.rb merge conflicts. A missing migration caught this way is also the most common source of schema drift between environments, which the scheduled checks in detecting production schema drift against a desired state are designed to find.
Verification Checklist
Frequently Asked Questions
Why did this start failing after upgrading to EF Core 9?
EF Core 9 changed PendingModelChangesWarning from a logged warning to an error by default when applying migrations. Differences between the model and the snapshot that were silently tolerated before now stop Migrate(), database update and bundles.
Is it safe to suppress the warning? It is possible but not advisable in production. The check exists to prevent deploying code whose model expects schema changes no migration makes. Fix the cause β add the migration or make the model deterministic β instead.
Why does HasData with DateTime.Now cause this?
Seed data is part of the model. A value computed at build or run time differs every time the model is built, so the model never matches the snapshot and every scaffolded migration contains updates to the seed rows.
Does the check look at the database? No. It compares the current code model with the model snapshot from the latest migration. The database can be fully up to date and the check can still fail, because the missing piece is a migration that has not been written yet.