Chapter 15 · Migrations, Model Snapshots, Seeding, Scripts, Bundles, and Deployment

Migration Bundles, Runtime Migration Risks, Permissions, Locking, and Multi-Instance Deployments

Build and operate migration bundles, understand EF migration locking and SQLite lock recovery, and enforce a single coordinated migration owner in multi-instance deployments.

Advanced145–185 minutesbundle + migration-lock deployment 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

Build framework-dependent and self-contained EF migration bundles for explicit target runtimes.

02

Explain how bundles locate configuration/connection strings and why secrets should be injected at deployment rather than committed.

03

Explain EF Core 9+ migration locking and distinguish it from application/schema compatibility during rolling deployment.

04

Recognize SQLite __EFMigrationsLock behavior and recover only a verified abandoned lock.

05

Compare bundles, scripts, CLI tools, and runtime migration as deployment strategies.

06

Design a single migration-owner workflow for multi-instance ServiceHub deployments.

1. The practical problem: production should not need source code and an SDK just to change schema

A deployment host may deliberately contain only the application artifacts and narrowly scoped operational tools. Installing the full .NET SDK, dotnet-ef, and source code merely to run migrations increases moving parts. A migration bundle packages EF's migration runner into a single executable generated during CI. A framework-dependent bundle needs a compatible runtime on the target; a self-contained bundle includes the runtime for a specific runtime identifier (RID).

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. Build bundles deliberately for the target topology

shell · framework-dependent bundle
mkdir -p artifactsdotnet ef migrations bundle   --project src/ServiceHub.EfLab   --output artifacts/efbundle
shell · self-contained Linux x64 bundle
dotnet ef migrations bundle   --project src/ServiceHub.EfLab   --self-contained   --target-runtime linux-x64   --output artifacts/efbundle-linux-x64

Do not build one platform binary and assume it is portable. The RID is an explicit deployment assumption. Pin the EF tool/package line used to create the bundle and archive the build metadata with the artifact.

3. Configuration and connection selection are deployment inputs

By default a bundle uses the application's connection configuration; Microsoft documentation warns to copy appsettings.json alongside the bundle when that is how the application supplies configuration. For real credentials, prefer secret injection or a protected --connection argument from the deployment environment rather than committing a production password to source or an artifact.

shell · apply to a disposable SQLite target
./artifacts/efbundle --connection "Data Source=servicehub-lab.db" --verbose

Command-line secrets can be visible to process inspection or logs depending on platform/runner. In production, use the deployment system’s supported secret mechanism and avoid echoing credentials. The lab connection string contains no secret.

4. Migration locking prevents simultaneous migration runners—not incompatible apps

EF Core 9 introduced migration locking for EF-driven migration application: CLI database update, bundles, and Migrate/MigrateAsync. The lock prevents multiple migration executions from modifying the schema concurrently. It does not make old and new application binaries automatically compatible with every intermediate schema state. You still need one migration owner and expand/contract compatibility where multiple versions overlap.

Mechanism What it protects What it does not protect
EF migration lock Two EF migration runners applying concurrently Old app querying a renamed/dropped column
DB transaction around migration work Provider-supported atomicity of migration operations All provider DDL/data operations in every scenario
Rolling deployment orchestration Ordering of app/schema rollout Incorrect migration code/data-loss intent
Backup/snapshot Recovery point Forward compatibility of old binaries

5. SQLite lock implementation has a real failure mode

SQLite has no native application lock like SQL Server sp_getapplock. EF creates __EFMigrationsLock and inserts a row. If a migration process is killed unexpectedly, the row/table can remain and later migration attempts can wait indefinitely. Recovery is an operational act: first prove no migration process is still active and inspect the database, then clear the abandoned lock according to provider guidance.

sql · SQLite provider-documented abandoned-lock recovery
-- Only after verifying no migration owner is active:DROP TABLE "__EFMigrationsLock";-- Provider documentation also permits deleting rows instead:-- DELETE FROM "__EFMigrationsLock";
Do not automate blind lock deletion

A lock that looks “old” may still protect an active deployment. Lock cleanup must be tied to process/deployment evidence and a runbook, not a timer that deletes coordination state.

6. Deliberately wrong: every replica is allowed to be migration owner

text · anti-pattern deployment topology
Replica A startup -> MigrateAsync()Replica B startup -> MigrateAsync()Replica C startup -> MigrateAsync()All three runtime identities have ALTER/DROP permissions.

The EF lock serializes migration execution, but the topology is still poor: all runtime identities are over-privileged, rollouts can wait on schema work, and application/schema compatibility is uncontrolled. Repair it by making migration a distinct deployment stage/job with exactly one owner. Only after migration verification should application replicas progress according to the compatibility plan.

7. Compare deployment strategies instead of choosing by convenience

Strategy Strength Important boundary
Reviewed SQL script SQL approval/archive/change-control friendly External runner; no EF migration lock; provider-specific idempotency.
Migration bundle Portable deployment artifact; EF locking/seeding behavior retained Must target correct runtime/provider/config; generated SQL not pre-reviewed unless separately scripted.
EF CLI database update Excellent local/test ergonomics Requires SDK/tool/source on execution host; not ideal production control plane.
Runtime MigrateAsync Simple application code path DDL permissions/startup coupling/compatibility risks; not the default production recommendation.

8. Hands-on lab: build, hash, run, and verify a bundle

  1. From the migration-complete teaching branch, run has-pending-model-changes.
  2. Build a framework-dependent bundle and record SHA-256 plus EF/dotnet-ef version.
  3. Copy appsettings.json if the lab factory/bundle expects it, or pass the disposable connection explicitly.
  4. Run the bundle against a copy of servicehub-lab.db; record applied migration output.
  5. Run the same bundle again; expect “already up to date”/no missing migrations.
  6. Inspect __EFMigrationsHistory.
  7. Review the SQLite lock-table runbook without manufacturing an abandoned lock on valuable data.

9. Production judgment and bridge to seeding/zero downtime

Bundles are strong automated deployment artifacts when you want EF to own migration execution without installing the SDK/source on the target. They are not a substitute for schema compatibility design, least privilege, backups, or operational ownership. Generate a separate reviewed SQL script if exact DDL review is required. The final chapter lesson combines migration ownership with deterministic data initialization and expand/contract release planning.

Check your understanding

  1. What does --self-contained change for a migration bundle?
  2. Does a bundle need source code or dotnet-ef installed on the deployment host?
  3. What does EF migration locking protect?
  4. How does SQLite implement EF migration locking?
  5. When is deleting __EFMigrationsLock appropriate?
  6. Why still prefer one migration owner?
Review the answers

1. It includes the .NET runtime for the selected target runtime/RID.

2. No; that is one of its main deployment advantages.

3. Concurrent EF-driven migration executions, not application/schema compatibility.

4. With the __EFMigrationsLock table/row.

5. Only after proving the lock is abandoned and no migration owner remains active.

6. It centralizes DDL permissions, rollout order, review, observability, and recovery decisions.

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