Chapter 15 · Migrations, Model Snapshots, Seeding, Scripts, Bundles, and Deployment
Generate Idempotent SQL Scripts and Separate Build-Time Migration Authoring from Deployment
Generate reviewed migration SQL with explicit source/target bounds, understand provider-specific idempotency, and separate schema-deployment credentials from the runtime application identity.
Learning outcomes
Generate migration scripts with explicit source and target migrations instead of assuming every database starts at the same point.
Explain idempotent migration scripts and state clearly that the EF SQLite provider cannot generate them.
Review generated provider SQL for destructive DDL, transaction boundaries, locks, and data movement before deployment.
Separate build-time migration authoring from production schema-deployment identity and runtime application permissions.
Explain why externally executed SQL scripts do not participate in EF migration locking.
Build a repeatable deployment manifest for the disposable ServiceHub SQLite lab.
1. The practical problem: the application artifact and schema artifact travel on different schedules
ServiceHub can build successfully while a target database is
one, two, or ten migrations behind. Production change control
may require SQL review by a database administrator (DBA), a
maintenance window, a separate DDL credential, or an archived
change ticket. Treating
dotnet ef database update on an application host as
the only deployment strategy collapses authoring, approval,
credentials, and execution into one opaque step.
Mandatory labs use .NET SDK 10.0.400, .NET runtime 10.0.11,
Microsoft.EntityFrameworkCore/SQLite 10.0.11, dotnet-ef
10.0.11, the disposable servicehub-lab.db,
deterministic ServiceHub seed data, the existing
Guid Revision concurrency token, shadow audit
fields, and the Chapter 12 outbox teaching model when
referenced. EF Core 11 previews are excluded. Production
credentials and databases are never used in destructive labs.
2. Bounded scripts make the source state explicit
mkdir -p artifactsdotnet ef migrations script PreviousMigration RenameDispatchToRouting --project src/ServiceHub.EfLab --output artifacts/Previous_to_RenameRouting.sql
A bounded script says: “apply this transition only if the
database is at PreviousMigration.” That is
appropriate for SQLite, whose provider cannot generate EF
idempotent scripts because SQLite lacks the procedural IF/THEN
mechanism EF uses for history-aware conditional migration
execution.
SELECT "MigrationId", "ProductVersion"FROM "__EFMigrationsHistory"ORDER BY "MigrationId";
3. Idempotent scripts are useful—but provider support is not universal
On providers that support them, --idempotent emits
history checks so only missing migrations are applied. This is
useful when a fleet of databases may start at different known
migration points. The same flag is
not a valid mandatory SQLite path.
dotnet ef migrations script --idempotent --project src/ServiceHub.EfLab --output artifacts/servicehub-idempotent.sql
Do not run this as the SQLite lab. Microsoft documents that
the SQLite provider cannot generate idempotent migration
scripts. For SQLite, know the current migration and generate a
bounded script, or use database update/a bundle
under controlled deployment ownership.
4. Review SQL as privileged deployment code
| Review question | Evidence to inspect | Why it matters |
|---|---|---|
| Does the script drop/replace data-bearing objects? |
DROP TABLE, DROP COLUMN, rebuild
sequence
|
May destroy or rewrite data. |
| Does SQLite rebuild a table? | Create-copy-drop-rename pattern | Can increase lock duration and temporary storage needs. |
| Are data transforms bounded? |
UPDATE predicates and expected row counts
|
Unbounded data fixes can lock/modify every row. |
| Are indexes/constraints created before data is valid? | DDL ordering | Can fail midway or block writes. |
| What transaction behavior does this provider/script use? | Generated transaction statements + provider docs | Atomicity assumptions affect recovery. |
| Is the script applied outside EF? | DBA/sqlite3/other runner | EF migration locking does not wrap external script execution. |
Do not infer performance from SQL text alone. For production-sized changes, test against realistic data volume and capture database-side timing/locking evidence using the production-like provider.
5. Separate identities: runtime DML is not deployment DDL
The application identity normally needs data access—SELECT/INSERT/UPDATE/DELETE and whatever stored routines are intentionally exposed. A deployment identity may need CREATE/ALTER/DROP permissions. Giving every application instance schema-owner permissions expands blast radius and makes startup migration tempting. The deployment pipeline should retrieve a short-lived or otherwise protected schema credential, apply the reviewed artifact, verify history/schema, and release that credential.
BUILD/CI identity -> compile model and migration code -> fail on pending model changes -> generate reviewed script or bundle -> sign/hash/archive artifactDEPLOY identity (DDL-capable, controlled) -> verify target migration -> apply approved artifact -> verify history + smoke queryRUNTIME identity (least privilege) -> normal application DML -> no routine schema modification
6. Deliberately wrong: every pod calls MigrateAsync at startup
using var scope = app.Services.CreateScope();var db = scope.ServiceProvider.GetRequiredService<ServiceHubContext>();await db.Database.MigrateAsync(); // do not make every app instance the migration owner
EF Core 9+ added migration locking, which reduces concurrent
migration corruption risk for Migrate, CLI updates,
and bundles. That does not erase the other operational problems:
the app needs DDL permissions, running binaries may be
incompatible with an in-progress schema change, generated SQL
receives no deployment-time review, and a blocked/long migration
can delay or fail many instances. Prefer one coordinated
migration owner.
7. CI should prove both migration completeness and artifact identity
dotnet restoredotnet build -c Releasedotnet ef migrations has-pending-model-changes --project src/ServiceHub.EfLabdotnet ef migrations list --project src/ServiceHub.EfLabdotnet ef migrations script PreviousMigration RenameDispatchToRouting --project src/ServiceHub.EfLab --output artifacts/schema.sql
Archive the migration IDs, EF/tool/provider versions, target provider, source/target migration, script hash, and review approval. If the migration artifact changes after review, its hash should change and approval should be repeated.
8. Hands-on lab: reviewed SQLite script deployment
- Create two copies of the disposable DB at the same known previous migration.
-
Generate a bounded
PreviousMigration → RenameDispatchToRoutingscript. - Review the SQL for rename/rebuild/drop operations and calculate a SHA-256 hash.
- Apply the script to one copy using a SQLite CLI/library path, not EF.
-
Verify
__EFMigrationsHistoryand data preservation. - Attempting to apply the same bounded script blindly to a database at an unexpected state should be treated as an operator error—not as “idempotence.”
- Document that external script execution does not acquire EF's migration lock.
sha256sum artifacts/schema.sql # Linux/macOS/Git Bash# PowerShell alternative:# Get-FileHash artifacts/schema.sql -Algorithm SHA256
9. Production judgment and bridge to bundles
Use reviewed SQL when humans or database change-control systems need to inspect/modify/archive the exact DDL. Use source/target bounds when the target state is known. Use idempotent scripts only on providers that support them and only after reviewing their generated logic. The next lesson covers migration bundles: executable deployment artifacts that retain EF's migration engine and locking behavior without requiring the SDK/source code on the target host.
Check your understanding
- Why generate a bounded migration script?
- Can EF Core generate idempotent migration scripts for SQLite?
- Does an externally executed SQL script acquire EF migration locking?
- Why separate runtime and deployment database identities?
- Does EF9+ migration locking make startup migration universally safe?
- What should invalidate script approval?
Review the answers
1. It records the expected source and target migration states explicitly.
2. No; the provider documents this limitation.
3. No. EF locking applies to EF-driven application methods/tools/bundles, not external script runners.
4. To keep routine application permissions least-privilege and restrict DDL capability to controlled deployment.
5. No. Permission, compatibility, review, rollout, and operational ownership risks remain.
6. Any change to the reviewed artifact/content/hash, target assumptions, or provider/version-sensitive behavior.
Authoritative references
Use primary documentation when regenerating or adapting these deployment steps; migration behavior is provider- and version-sensitive.