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

Deterministic Data Seeding, Environment-Specific Data, Rollback Strategy, and Zero-Downtime Thinking

Seed deterministic reference data with EF Core 10 mechanisms, separate environment data, and design rollback/expand-contract deployments that keep old and new application versions compatible.

Advanced160–210 minutesseeding + expand/contract 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

Choose between UseSeeding/UseAsyncSeeding, HasData/model-managed data, custom initialization, and manual migration data operations.

02

Use deterministic identifiers and idempotent checks for reference data without embedding environment secrets or operational tenant data in model snapshots.

03

Explain how EF migration locking interacts with UseSeeding/UseAsyncSeeding.

04

Design forward-fix/restore rollback plans instead of assuming Down migrations recover business data.

05

Apply expand/contract sequencing so old and new application versions can coexist during rolling deployment.

06

Assemble a Chapter 15 deployment checklist covering model, migration, artifact, permissions, locking, seed state, verification, and recovery.

1. The practical problem: schema is correct, but the application still cannot start safely

ServiceHub may need stable reference rows—work-order priorities, system-owned tag definitions, or migration-era feature metadata—before application traffic uses a new feature. That is different from production tenant/customer data, secrets, demo fixtures, or large operational imports. “Seeding” is not one mechanism; EF Core exposes several choices with different snapshot, deployment, locking, and environment behavior.

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. Four initialization mechanisms serve different ownership models

Mechanism Good fit Important boundary
UseSeeding / UseAsyncSeeding Imperative initialization tied to EF database initialization/migration flow EF9+; run under migration locking; implement both sync and async counterparts.
HasData model-managed data Small deterministic reference rows whose values belong in model/migrations Changes become migration operations; deterministic keys required; poor fit for secrets/large mutable data.
Manual migration operations / Sql Schema-coupled one-time transforms/backfills Privileged deployment code; provider/data-volume sensitive.
Application/import workflow Environment/tenant/operational data with independent lifecycle Do not make every app startup rewrite environment data.

EF documentation recommends UseSeeding/UseAsyncSeeding as the general seeding location for EF-managed initialization. Tooling invokes the synchronous seeding path in some flows, so implementing only the async delegate is an avoidable operational surprise.

3. Deterministic reference data: stable keys, no secrets

Suppose ServiceHub needs system priority definitions. Use stable keys that do not change between environments. Do not use Guid.NewGuid() inside HasData or other model-building paths; that makes every model build appear different and can trigger pending-model/migration churn.

csharp · teaching reference-data type
public sealed class WorkOrderPriority{    public int Id { get; init; }    public required string Code { get; init; }    public required string DisplayName { get; init; }}// In OnModelCreating / IEntityTypeConfiguration:modelBuilder.Entity<WorkOrderPriority>(builder =>{    builder.ToTable("work_order_priorities");    builder.HasKey(x => x.Id);    builder.HasIndex(x => x.Code).IsUnique();    builder.Property(x => x.Code).HasMaxLength(24);    builder.Property(x => x.DisplayName).HasMaxLength(80);});
csharp · deterministic model-managed reference rows
builder.HasData(    new WorkOrderPriority { Id = 1, Code = "normal", DisplayName = "Normal" },    new WorkOrderPriority { Id = 2, Code = "urgent", DisplayName = "Urgent" });
Keep environment data out of model-managed seed

Production endpoints, tenant/customer rows, API keys, connection strings, rotating credentials, and large operational imports do not belong in model snapshots/migration source. Use environment-specific provisioning or application workflows with their own authorization/audit model.

4. UseSeeding/UseAsyncSeeding for imperative, idempotent initialization

csharp · EF Core 10 seeding pair
optionsBuilder    .UseSqlite(connectionString)    .UseSeeding((context, _) =>    {        if (!context.Set<WorkOrderPriority>().Any(x => x.Code == "normal"))        {            context.Set<WorkOrderPriority>().Add(                new WorkOrderPriority { Id = 1, Code = "normal", DisplayName = "Normal" });            context.SaveChanges();        }    })    .UseAsyncSeeding(async (context, _, ct) =>    {        if (!await context.Set<WorkOrderPriority>()                .AnyAsync(x => x.Code == "normal", ct))        {            context.Set<WorkOrderPriority>().Add(                new WorkOrderPriority { Id = 1, Code = "normal", DisplayName = "Normal" });            await context.SaveChangesAsync(ct);        }    });

The predicate is deliberately idempotent for the small lab. For multiple reference rows, use unique constraints and a robust upsert/provisioning strategy appropriate to the provider. Seeding is executed as part of EF initialization/migration operations and is protected by the migration locking mechanism; it should still be observable and bounded.

5. Deliberately wrong: random model data and production secrets in HasData

csharp · wrong: nondeterministic + secret-bearing model data
builder.HasData(new IntegrationEndpoint{    Id = Guid.NewGuid(),               // model changes on every build    Name = "production-broker",    ApiKey = "real-secret-here"        // secret committed into migration/snapshot});

This can create perpetual pending model changes and leak sensitive values into source control/migration history. Repair it by keeping the EF model deterministic and moving secrets/environment-specific resources to the deployment platform’s secret/configuration system.

6. Rollback is a business recovery plan, not “run Down and hope”

A deployment can fail because of code bugs, data incompatibility, long-running backfills, or a migration defect. Decide recovery before release:

Failure type Preferred recovery question Why Down alone may fail
Pure additive schema + app bug Can old app run against expanded schema while new app is rolled back? No schema rollback may be needed.
Destructive migration Is there a verified backup/snapshot and restore time objective? Dropped/transformed data may not be reconstructable.
Bad backfill Can a corrective forward migration identify/repair affected rows? Down may not know original values.
Partially external side effect What compensation/reconciliation runbook exists? Database migration rollback cannot undo external systems.

For many production incidents, forward-fix or application rollback against a backward-compatible expanded schema is safer than immediately reversing DDL.

7. Expand/contract: make zero-downtime compatibility a sequence

text · ServiceHub routing-code rollout
Release N: EXPAND  1. Add nullable routing_code; keep dispatch_code.  2. Backfill existing rows in a measured deployment step.  3. Deploy app version that can read old/new and writes the compatibility plan.Release N+1: SWITCH  4. Make routing_code authoritative after all old writers are gone.  5. Observe missing/invalid routing_code metrics and repair stragglers.Release N+2: CONTRACT  6. Remove legacy dispatch_code reads/writes.  7. Only then drop dispatch_code in a separately reviewed migration.

Zero downtime is not guaranteed by EF. It emerges from application/schema compatibility, provider lock characteristics, data-migration duration, deployment topology, and observability. Large backfills may need chunking or a separate operational job rather than one giant migration transaction.

8. Chapter capstone lab: one deployable migration package

  1. Run dotnet ef migrations has-pending-model-changes; it must be clean after intended migrations are scaffolded.
  2. List migrations and record the intended source/target migration.
  3. For SQLite, generate a bounded reviewed script; do not claim --idempotent support.
  4. Build a migration bundle and record its SHA-256, target RID if self-contained, EF/tool/runtime versions, and config assumptions.
  5. Apply to a copy of the disposable DB with the deployment identity/path.
  6. Verify __EFMigrationsHistory, expected columns/indexes, deterministic seed rows, and smoke queries.
  7. Run the deployment a second time and verify no duplicate seed/reference rows appear.
  8. Document rollback/restore and abandoned SQLite migration-lock recovery procedures.
shell · pre-release evidence commands
dotnet ef migrations has-pending-model-changes --project src/ServiceHub.EfLabdotnet ef migrations list --project src/ServiceHub.EfLabdotnet ef migrations script PreviousMigration LatestMigration   --project src/ServiceHub.EfLab   --output artifacts/servicehub-bounded.sqldotnet ef migrations bundle --project src/ServiceHub.EfLab   --output artifacts/efbundle

9. Production judgment and bridge to reverse engineering

A mature migration workflow has an owner, source-controlled migration code/snapshot, provider-specific review, CI pending-model gate, deployment artifact, restricted DDL identity, migration-state verification, locking/runbook awareness, deterministic reference-data strategy, backward-compatible rollout plan, and tested recovery. Never equate a successful database update with a safe release. Chapter 16 now reverses the direction: rather than evolving a model-first schema, it will inspect existing databases and scaffold EF models from them while preserving sustainable ownership.

Check your understanding

  1. What EF Core seeding APIs should usually be implemented together?
  2. Why are random keys dangerous in model-managed HasData?
  3. Should production secrets be placed in HasData?
  4. Does Down guarantee recovery from a destructive migration?
  5. What is the purpose of expand/contract?
  6. What proves a migration package is ready?
Review the answers

1. UseSeeding and UseAsyncSeeding, because tooling/runtime paths can invoke different forms.

2. They make the model appear different between builds and can generate perpetual migration changes.

3. No. They become source/migration artifacts; use a secret/provisioning system.

4. No. Restore, forward-fix, or compensating data procedures may be required.

5. Keep old/new app versions compatible with an intermediate schema during staged/rolling deployment.

6. Clean pending-model check, reviewed artifact, known source/target, provider/version metadata, deployment permissions, seed verification, smoke tests, and a recovery/runbook plan.

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