Skip to content

Build1 publisher2 min readPublished

EF Core's migration differ proposes dropping a table the CLI process cannot see

Run EF Core's command-line tooling from the module that owns the DbContext and the contributed table disappears from the model it builds. A dev.to post treats the resulting warning as a visibility gap and replaces it with an executable guard.

The Engineer · Build desk

Illustration accompanying EF Core's migration differ proposes dropping a table the CLI process cannot see

What happened

  • A lower-level persistence module owns the EF Core context, and higher-level modules contribute their entity configurations at runtime through discovery, so the lower module holds no references back to them.
  • Run the migration tooling from that lower-level project and the contributing assembly is absent, because adding the reference would create a project cycle, so the tool builds a valid but partial model.
  • The differ finds the contributed table in the committed snapshot and not in the model it just built, and proposes the operation that reconciles the two views: dropping the table.
  • The dev.to post's response is to let the database-update path proceed under that one documented condition, while a separate full-model comparison stays the authoritative drift test.
  • Its executable guard reads the operations in every committed migration and fails on a drop of the protected table, a rename out of the contract, a column removal on it, or raw SQL carrying the destructive statement.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint The acyclic project graph that keeps the modules decoupled is the same thing that bounds what the tooling can load, so the partial design-time model is a standing property of the architecture.
  • decision The cheap way to clear the warning is to regenerate the snapshot, and the post argues that is the one move to avoid here. A scoped suppression is the choice that remains.
  • cost Suppressing the pending-model warning in a design-time factory spends a safety signal, and the team then owes a replacement test aimed at the exact operation the warning was standing in for.
  • precedent Any test asserting that nothing bad exists now needs a companion proving its search space was populated; the post extends the pairing to dependency scans, authorization maps and route inventories.

Both artifacts in this comparison are accurate. The snapshot was generated from the full application model, and the dev.to post is explicit that the contributed table has to stay there for every host that runs on the shared schema [5]. The differ cannot tell an assembly that failed to load from an entity somebody deleted, because the design-time model it reads is a projection of the running application [1]. So the same warning can be accurate about the model in front of it and dangerous about the operation it recommends [2].

The diagnostic order matters. Read the entire proposed difference first, and generate a probe migration to make it concrete [8]. If the only operation is removal of a table supplied by a module the tooling process cannot load, the warning describes a visibility gap [9]. Anything else in that diff can still be real drift [10].

The post refuses the broad version of the excuse. Calling the warning a false positive is too wide a claim; what may be a false positive is one warning, under a documented loading boundary [10]. The failure being defended against is equally specific: a migration that drops, renames or damages a table the design-time process cannot see [13].

The guard has a failure mode of its own: a scan that reports no forbidden operation passes when the migrations are clean, and also when discovery found no migrations at all, which the post calls a vacuous pass [15]. The answer is a second test proving the scan sees the migration that originally created the protected table, so the pair asserts both that the evidence set is present and that nothing in it breaks the invariant [16].

Coverage is what the guard leaves open. It watches one destructive edge and cannot show that every legitimate model change has a migration behind it, so the post keeps a snapshot comparison running in a process where all runtime contributors are loaded [18].

Before any of this transfers to another repository, the loading boundary has to be written down, because the exception is scoped to that boundary and not to the warning class [10]. The protected table has to be named in the guard, because the guard knows only the four operation shapes it enumerates [14] [19]. And the authoritative comparison has to run in a process that loads the contributors [18], and the migration CLI does not load them [4].

What to watch

  • Whether EF Core tooling gains a way to load contributed configurations without a project reference. That would remove the loading boundary the exception depends on.
  • A second module contributing a second table: the guard's protected-table list becomes a maintenance item and the narrow suppression widens.
  • Whether teams actually run the full-model snapshot comparison in a host process in CI, or let the suppressed warning stand as the only check.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories