Describe the bug
DatabaseFacade.HasPendingModelChanges() (runtime) reports pending model changes for every DbContext that has one or more SQL Server temporal tables (.ToTable(b => b.IsTemporal())), even immediately after scaffolding a migration that exactly matches the current model. The authoritative CLI equivalent, dotnet ef migrations has-pending-model-changes, correctly reports no changes for the same context/model/migration-set combination.
Dumping the raw IMigrationsModelDiffer.GetDifferences(...) output for the runtime path shows the "differences" are exclusively temporal period-column churn — never a real schema difference:
- A context with 21 temporal tables produces 63 spurious operations: 21
AlterTableOperation (toggling the table's temporal-ness) + 42 AddColumnOperation for the shadow PeriodStart/PeriodEnd properties (2 per table).
- A context with 1 temporal table produces 3 spurious operations (1
AlterTableOperation + 2 AddColumnOperation for PeriodStart/PeriodEnd).
- Contexts with zero temporal tables report clean via both paths.
This makes HasPendingModelChanges() unusable as an in-process CI/test guard against model/migration drift for any context using temporal tables — the only reliable way we've found to detect real drift is running the CLI command out-of-process per context.
Root cause (as far as we've diagnosed)
It looks like an asymmetry in how the snapshot model (deserialized from the last migration's *ModelSnapshot.cs) vs. the current/design-time model are finalized for temporal-table metadata before being diffed:
- The CLI (
dotnet ef migrations has-pending-model-changes) builds its "current model" through the design-time services pipeline and gets a clean diff.
- The runtime path (
DatabaseFacade.HasPendingModelChanges()) finalizes the current model via IModelRuntimeInitializer.Initialize(...), and the resulting runtime model's temporal annotations (or the migrations differ's interpretation of them) disagree with the snapshot model's, even though both ultimately describe the identical schema.
We attempted the one documented lever for this — initializing the snapshot model with IModelRuntimeInitializer.Initialize(model, designTime: true) before diffing — and it did not resolve the discrepancy.
To Reproduce
We don't yet have a minimal standalone repro project to attach, but the shape that reproduces it every time in our codebase is:
- A
DbContext with at least one entity mapped via .ToTable(b => b.IsTemporal()) (SQL Server provider).
- A migration set that is genuinely up to date (
dotnet ef migrations has-pending-model-changes reports "No changes have been made to the model since the last migration." for the context).
- At runtime, call
context.Database.HasPendingModelChanges() — this returns true.
- Diffing the resolved models directly via
context.GetService<IMigrationsModelDiffer>().GetDifferences(snapshotModel.GetRelationalModel(), currentModel.GetRelationalModel()) shows only AlterTableOperation + AddColumnOperation/DropColumnOperation entries for the temporal table(s) and their PeriodStart/PeriodEnd shadow columns.
We can put together a minimal isolated repro if that would help triage — happy to do so on request.
Further technical details
EF Core version: 10.0.7 (also reproduced with the dotnet-ef CLI tool at 10.0.9)
Database provider: Microsoft.EntityFrameworkCore.SqlServer 10.0.7
Target framework: net10.0
Operating system: Windows (CI agents), reproduces locally on Windows dev machines too
Related issues
This looks adjacent to, but distinct from:
We couldn't find an existing issue describing this specific runtime-vs-CLI divergence for temporal tables, so filing separately. Happy to close as a duplicate if one already exists that we missed.
Describe the bug
DatabaseFacade.HasPendingModelChanges()(runtime) reports pending model changes for everyDbContextthat has one or more SQL Server temporal tables (.ToTable(b => b.IsTemporal())), even immediately after scaffolding a migration that exactly matches the current model. The authoritative CLI equivalent,dotnet ef migrations has-pending-model-changes, correctly reports no changes for the same context/model/migration-set combination.Dumping the raw
IMigrationsModelDiffer.GetDifferences(...)output for the runtime path shows the "differences" are exclusively temporal period-column churn — never a real schema difference:AlterTableOperation(toggling the table's temporal-ness) + 42AddColumnOperationfor the shadowPeriodStart/PeriodEndproperties (2 per table).AlterTableOperation+ 2AddColumnOperationforPeriodStart/PeriodEnd).This makes
HasPendingModelChanges()unusable as an in-process CI/test guard against model/migration drift for any context using temporal tables — the only reliable way we've found to detect real drift is running the CLI command out-of-process per context.Root cause (as far as we've diagnosed)
It looks like an asymmetry in how the snapshot model (deserialized from the last migration's
*ModelSnapshot.cs) vs. the current/design-time model are finalized for temporal-table metadata before being diffed:dotnet ef migrations has-pending-model-changes) builds its "current model" through the design-time services pipeline and gets a clean diff.DatabaseFacade.HasPendingModelChanges()) finalizes the current model viaIModelRuntimeInitializer.Initialize(...), and the resulting runtime model's temporal annotations (or the migrations differ's interpretation of them) disagree with the snapshot model's, even though both ultimately describe the identical schema.We attempted the one documented lever for this — initializing the snapshot model with
IModelRuntimeInitializer.Initialize(model, designTime: true)before diffing — and it did not resolve the discrepancy.To Reproduce
We don't yet have a minimal standalone repro project to attach, but the shape that reproduces it every time in our codebase is:
DbContextwith at least one entity mapped via.ToTable(b => b.IsTemporal())(SQL Server provider).dotnet ef migrations has-pending-model-changesreports "No changes have been made to the model since the last migration." for the context).context.Database.HasPendingModelChanges()— this returnstrue.context.GetService<IMigrationsModelDiffer>().GetDifferences(snapshotModel.GetRelationalModel(), currentModel.GetRelationalModel())shows onlyAlterTableOperation+AddColumnOperation/DropColumnOperationentries for the temporal table(s) and theirPeriodStart/PeriodEndshadow columns.We can put together a minimal isolated repro if that would help triage — happy to do so on request.
Further technical details
EF Core version: 10.0.7 (also reproduced with the
dotnet-efCLI tool at 10.0.9)Database provider: Microsoft.EntityFrameworkCore.SqlServer 10.0.7
Target framework: net10.0
Operating system: Windows (CI agents), reproduces locally on Windows dev machines too
Related issues
This looks adjacent to, but distinct from:
HasPendingModelChanges()always returns true — root-caused there to a provider mismatch between the scaffolded migration and the context under test; not our scenario, since our context and migrations both target SQL Server)PeriodStartandPeriodEndcolumns of a temporal table causes them to be swapped #29902 / Renaming TemporalTable PeriodStart/PeriodEnd maps to incorrect value #32705 (temporalPeriodStart/PeriodEndnaming/mapping issues — related area, different symptom)We couldn't find an existing issue describing this specific runtime-vs-CLI divergence for temporal tables, so filing separately. Happy to close as a duplicate if one already exists that we missed.