Idempotent EF migration script for 58→64 fails: AddAuditLogHashChain adds ChainSeq and indexes it in one batch (Msg 207) #126
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found preparing the 2026-11-11 vd03 (DEV) deploy
0e540291→424db155(migrations 58 → 64).The output of
dotnet ef migrations script --idempotentfails on SQL Server with Msg 207 "Invalid column name 'ChainSeq'".AddAuditLogHashChainadds the column and creates indexes on it inside the same batch, and SQL Server compiles the whole batch before theIF NOT EXISTSguard runs. The script deployed to vd03 adds aGOafter each guarded block andSET XACT_ABORT ONat the top. Forward, re-run (no-op), rollback and forward again were all rehearsed against a restored copy of the vd03 database.Fix options:
migrationBuilder.Sqland a deferredEXEC; orGO-splitting step in the deploy runbook and ship a post-processing step.Also check
docs/plans/sql/AddAuditLogHashChain.sql, which likely has the same problem (untested). QA/prod deploys will hit this.Fixed on
maininc7dfc21b(plus test fix7bc35081).Root cause. EF 10's
--idempotentoutput puts each migration in one batch (BEGIN TRANSACTION ... COMMIT; GO), with anIF NOT EXISTS (__EFMigrationsHistory)guard per statement. SQL Server compiles the whole batch before any guard runs. InAddAuditLogHashChain, the two raw-SQL filtered indexes (WHERE ChainSeq IS [NOT] NULL) reference a column the same batch adds, which raises Msg 207.database update/Migrate()are unaffected because they send one command at a time.What changed
20261109211317_AddAuditLogHashChain.cs: bothCREATE NONCLUSTERED INDEXstatements are now wrapped inEXEC(N'...'), which defers compilation. The index definitions, the model snapshot and the Designer are unchanged. A DB that already has the migration (vd03) never runs it again.GRANT UPDATE (ChainSeq, RowHash)didn't need wrapping because it is checked at execution time.AddConfigGraphRowVersionsadds RowVersion and indexes it in one batch, but a plainCREATE INDEXon a new column is not compile-checked. The script from 57 to head now runs clean.docs/plans/sql/AddAuditLogHashChain.sql(hand-written prod script): had the same problem, now fixed with the same EXEC wrap. It andPrefixSearchIndexes.sql/AddConfigGraphRowVersions.sqlalso had a stray UTF-8 BOM right after the prepended header comment, so all three failed under sqlcmd with Msg 102Incorrect syntax near ''before reaching any SQL. The BOMs are removed.IdempotentMigrationScriptTests(MSSQL fixture): migrates to 57, runs the generated idempotent script batch by batch, asserts all 64 migrations are applied, then re-runs the script as a no-op.FlipAuditLogClusteredKeyMigrationTests, which was order-dependent since migration 64. Its Down/Up revertedAuditMaintenanceGrantsand only went back up to the flip, so theUPDATE (SourceNode)grant assertion failed in one order or the other. It now restores to head.Evidence (local throwaway
mssql/server:2022container plus the localscadabridge-mssqltest server; no wonder box touched)AddSiteArtifactRevision(57) on a migration-57 DB fails withMsg 207 ... Invalid column name 'ChainSeq'(batch 4).dotnet ef database updatewith the original migration, (c)database updatewith the fixed migration. Identical (622 lines each).ReviewRemediationIndexes -> PrefixSearchIndexes -> AddAuditLogHashChain -> AddConfigGraphRowVersions -> FlipAuditLogClusteredKey(+ EF script for 64) now runs clean, and the schema matches (b) exactly. The old hash-chain script (with the BOM removed) still fails with Msg 207, which was the control.Invalid column name 'ChainSeq'when the migration change is reverted, and passes with it.dotnet build ZB.MOM.WW.ScadaBridge.slnx: 0 warnings, 0 errors. ConfigurationDatabase.Tests 536/536; AuditLog.Tests chain filter 26/26; ManagementService VerifyAuditChain 13/13.Deploy note. QA/prod can now run the generated idempotent script as-is, with no GO insertion. Keep
SET XACT_ABORT ON, and run undersqlcmd -I -b.Left open: the full-history script (from an empty DB) still fails in pre-58 migrations. That is out of scope here because every environment is at >= 57; it is filed as #127.