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.

Advanced135–175 minutesreviewed script + permission-boundary labEF Core 10.0.11 · .NET 10.0.11SQLite provider 10.0.11 mandatory baselinedotnet-ef 10.0.11 · SDK 10.0.400Last reviewed: August 2026

Learning outcomes

01

Generate migration scripts with explicit source and target migrations instead of assuming every database starts at the same point.

02

Explain idempotent migration scripts and state clearly that the EF SQLite provider cannot generate them.

03

Review generated provider SQL for destructive DDL, transaction boundaries, locks, and data movement before deployment.

04

Separate build-time migration authoring from production schema-deployment identity and runtime application permissions.

05

Explain why externally executed SQL scripts do not participate in EF migration locking.

06

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.

Reproducible baseline

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

shell · generate a known-state SQLite script
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.

sql · verify the target starts where the script expects
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.

shell · optional provider-supported path, e.g. SQL Server
dotnet ef migrations script --idempotent   --project src/ServiceHub.EfLab   --output artifacts/servicehub-idempotent.sql
SQLite limitation

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.

text · conceptual deployment sequence
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

csharp · wrong production default
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

shell · migration gates before packaging
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

  1. Create two copies of the disposable DB at the same known previous migration.
  2. Generate a bounded PreviousMigration → RenameDispatchToRouting script.
  3. Review the SQL for rename/rebuild/drop operations and calculate a SHA-256 hash.
  4. Apply the script to one copy using a SQLite CLI/library path, not EF.
  5. Verify __EFMigrationsHistory and data preservation.
  6. 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.”
  7. Document that external script execution does not acquire EF's migration lock.
shell · portable hash example
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

  1. Why generate a bounded migration script?
  2. Can EF Core generate idempotent migration scripts for SQLite?
  3. Does an externally executed SQL script acquire EF migration locking?
  4. Why separate runtime and deployment database identities?
  5. Does EF9+ migration locking make startup migration universally safe?
  6. 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.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.

\n