Skip to content Skip to footer

Fixing EF Core “PendingModelChangesWarning” When Applying Migrations Automatically

When you auto-run EF Core migrations on application startup, one of the most confusing failures you can hit is:

The model for context 'XYZContext' has pending changes. Add a new migration before updating the database.

This is PendingModelChangesWarning being escalated to an exception. In simple setups it’s annoying; in multi‑context or multi‑tenant setups it can completely block startup.

This article explains:

  • What PendingModelChangesWarning really means
  • Why it breaks auto-migrations in real systems
  • Three practical strategies to fix it
  • How and when it’s reasonable to ignore the warning

1. What PendingModelChangesWarning Actually Means

EF Core compares:

  • The current model (your DbContext + OnModelCreating + discovered entities)
  • The applied migrations recorded in __EFMigrationsHistory

If EF can see that the model has changed without a corresponding migration, it raises RelationalEventId.PendingModelChangesWarning.

If you call:

await context.Database.MigrateAsync();

EF will, by default, treat that warning as fatal and throw an InvalidOperationException. That’s what you see at startup.

Typical causes:

  • You changed an entity (added property, changed type, etc.) but did not run:dotnet ef migrations add SomeName -c YourContext
  • You deleted or modified a migration file by hand.
  • You are using multiple contexts and only added a migration for one of them.

2. Why It Breaks Auto-Migrations on Startup

A common pattern in ASP.NET Core Program.cs:

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var dbAuthContext = services.GetRequiredService<DBAuthContext>();
    await dbAuthContext.Database.MigrateAsync();

    var dbContext = services.GetRequiredService<AppDbContext>();
    await dbContext.Database.MigrateAsync();
}

This is fine until:

  • The DBAuthContext model changes and no migration exists yet.
  • EF detects the mismatch and throws before any migration is applied.

In more advanced setups (multi‑tenant / per‑organisation DBs), it gets worse:

  • To determine a tenant connection string, you query some shared table (e.g. BusinessOrganisations) in DBAuthContext.
  • That entity adds new properties (e.g. SupersetBaseUrl).
  • The model now includes the new columns, but the physical table does not yet.
  • When startup code queries BusinessOrganisations, EF translates it into a SELECT that references the new columns → PostgreSQL says “column ... does not exist” → startup blows up before migrations run.

You end up in a chicken‑and‑egg situation:

  • You need the new schema to boot correctly.
  • But you can’t run migrations because EF refuses due to “pending model changes”.

3. Strategy 1 – The Clean Way: Always Add Migrations

The most correct approach:

  1. Every time you touch a DbContext model, run:dotnet ef migrations add SomeDescriptiveName -c DBAuthContext dotnet ef migrations add SomeDescriptiveName -c YourOtherContext
  2. Then apply them:
    • Manually: dotnet ef database update -c DBAuthContext
    • Or automatically: context.Database.Migrate() at startup.
  3. Never alter schema directly by SQL outside migrations.

This keeps:

  • Model ✅
  • Migrations ✅
  • Database schema ✅

…in lockstep.

Trade‑off: In fast‑moving dev environments, you sometimes intentionally skip a migration (e.g. work‑in‑progress, temporary experiment), and the warning kills your app. That’s where Strategy 2/3 come in.


4. Strategy 2 – Suppress PendingModelChangesWarning for a Specific Context

If you are comfortable that:

  • You control migrations,
  • You do not want EF to block on “model changed” in a given context at runtime,

you can suppress PendingModelChangesWarning in that context’s configuration.

Example in Program.cs for an auth/context used purely for migrations and lookup:

using Microsoft.EntityFrameworkCore.Diagnostics;

// DBAuthContext configuration
builder.Services.AddDbContext<DBAuthContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("AuthConnection"))
           .ConfigureWarnings(warnings =>
               warnings.Ignore(RelationalEventId.PendingModelChangesWarning)));

What this does:

  • EF will still log the pending changes warning if you enable diagnostic logging.
  • But it will not throw when you call Database.Migrate().

This is reasonable when:

  • The context is only used at startup or for low‑risk data (e.g. tenant registry / auth metadata).
  • You still use migrations in your CI/CD process to keep schema in sync.
  • You accept the risk that a developer might forget to add a migration; the app will still run, and you’ll catch it in tests or via logs.

Important: Do not sprinkle this globally across all contexts unless you have very strong process discipline around migrations. It’s easy to drift.


5. Strategy 3 – Avoid Querying Future Columns Before Migration (Projection Trick)

In multi‑tenant scenarios, you often have a “bootstrap” query that runs before migrations:

// Conceptual example
var organisation = await authContext.BusinessOrganisations
    .FirstOrDefaultAsync(bo => bo.Id == orgGuid);

EF will generate a SELECT with all mapped columns:

SELECT "Id", "Name", ..., "SupersetBaseUrl", ...
FROM "BusinessOrganisations"
WHERE "Id" = @orgGuid

If the new columns don’t exist yet, the DB fails the query with column ... does not exist.

The fix is to only select the columns you know exist and that you actually need for bootstrap:

var org = await authContext.BusinessOrganisations
    .Where(bo => bo.Id == orgGuid)
    .Select(bo => new
    {
        bo.Id,
        bo.Identifier,
        bo.IsSubscriptionActive,
        bo.DatabaseConnectionString
    })
    .FirstOrDefaultAsync();

Key points:

  • EF now generates SQL that only uses those four columns.
  • New columns (e.g. SupersetBaseUrl) are not referenced, so the query succeeds even if the migration hasn’t added them yet.
  • You can safely run migrations after that, at startup, without a chicken‑and‑egg failure.

This pattern is extremely useful when:

  • You use a “meta‑database” for tenant info, connection strings, or feature flags.
  • You add properties frequently to those entities but don’t want every change to block bootstrapping.

6. Combining Both: Suppress Warning + Defensive Bootstrap Query

In real, production‑grade systems, the pragmatic solution is often to combine:

  • Warning suppression for the bootstrap context (DBAuthContext in the example).
  • Selective projections to avoid querying columns that may not yet exist.

The net effect:

  • On startup, your auth/meta context can still connect and run.
  • Tenant connection strings are loaded using only stable, essential columns.
  • After that, your main app context(s) can call Database.Migrate() and apply new migrations safely.
  • You avoid hard startup failures from “pending model changes” and “column does not exist”.

7. When Is It Safe to Ignore PendingModelChangesWarning?

Ignoring it is acceptable if:

  • Migrations are part of your build or deployment process (CI runs dotnet ef migrations add / update as needed).
  • You have monitoring or logs where the warning is still visible to developers.
  • You’re not using EF migrations as your only source of truth for schema (e.g. DBAs review & apply migrations separately).

It is not acceptable if:

  • Developers frequently modify entities without adding migrations.
  • You rely solely on auto‑migrate in production and have no schema checks.
  • Schema drift would be catastrophic (e.g. financial, inventory, compliance‑sensitive systems) without strong governance.

8. Recommended Practice

  • For typical line‑of‑business apps:
    • Keep PendingModelChangesWarning enabled by default.
    • Auto‑migrate on startup for dev and integration environments.
    • In production, apply migrations explicitly as part of deployment.
  • For complex, multi‑tenant or bootstrap‑heavy systems (like yours):
    • Use strategy 3 (projection) to avoid querying future columns.
    • Consider strategy 2 (suppress warning) for the bootstrap/auth context only.
    • Keep main business contexts strict (no suppression) so you don’t accumulate silent mismatches.

Leave a Comment