Skip to main content

Database migrations

:::info Status Outline — expand with a worked add-a-migration example. :::

Your configuration holds one shared EF Core model with a DbContext subclass per provider you selected at scaffold time (--IncludeSqlite/--IncludeSqlServer/--IncludePostgres) and separate migrations per provider under Acme.Persistence.Databases/Migrations/{Sqlite,SqlServer,PostgreSql}/, kept in lockstep. Regenerate all of them together whenever the model changes.

kandra-migrate — the tool for this

KandraEfMigrationTool (command kandra-migrate) wraps dotnet ef and applies a given operation uniformly across every EF Core provider you enabled — one command instead of one dotnet ef invocation per provider. Your scaffolded repo already ships the migrations.manifest.json it reads (resolved provider list, project/startup-project/context names per provider — see the tool's README for the schema) so it works out of the box once installed:

dotnet tool restore --add-source <your-nuget-feed> # once the package is reachable from your feed setup
dotnet kandra-migrate doctor # sanity-check the manifest + every enabled provider's DbContext
dotnet kandra-migrate list
dotnet kandra-migrate status # pending-model-change gate, CI-friendly
dotnet kandra-migrate add <MigrationName>
dotnet kandra-migrate remove
dotnet kandra-migrate script --output-dir artifacts/sql
dotnet kandra-migrate update --provider Sqlite --environment Development
dotnet kandra-migrate drop --provider Sqlite

Notes on the less obvious commands:

  • update requires both --provider and --environment (there's no "update all providers" — a running instance targets one real database at a time). It resolves the real per-environment connection string itself rather than trusting DOTNET_ENVIRONMENT to boot your app's host — every Kandra configuration's design-time factory hardcodes a local/dev connection string that would otherwise win regardless of environment. Production always prompts for a typed confirmation, even with --yes.
  • drop has no --environment at all — dotnet ef database drop has no --connection override, so it can only ever touch the design-time factory's hardcoded local database, making it structurally local/dev-only. It requires --provider and typing the resolved database identifier back to confirm, unless --yes.
  • squash isn't built yet — don't try to collapse migration history by hand either; see the tool's README for why that's deliberately being built carefully rather than rushed.

Fallback: dotnet ef directly

If kandra-migrate isn't installed/reachable yet in your setup, fall back to dotnet ef per provider, only for the provider(s) you actually included at scaffold time:

cd src/Acme.Persistence.Databases
dotnet ef migrations add <MigrationName> --project . --startup-project ../Acme.WebApi/Acme.WebApi.csproj --context AcmeDbContextSqlite --output-dir Migrations/Sqlite
dotnet ef migrations add <MigrationName> --project . --startup-project ../Acme.WebApi/Acme.WebApi.csproj --context AcmeDbContextSqlServer --output-dir Migrations/SqlServer
dotnet ef migrations add <MigrationName> --project . --startup-project ../Acme.WebApi/Acme.WebApi.csproj --context AcmeDbContextPostgreSql --output-dir Migrations/PostgreSql

The project's own Providers csproj property is computed from which Contexts/*.cs files exist on disk, so it always reflects reality even if you add or remove a provider by hand later — there's no separate config to keep in sync, whichever path (tool or direct) you use.

Gotcha: the abstract DocumentBase guard

A self-healing guard in AcmeDbContext.OnModelCreating (in Acme.Persistence) unmaps the engine's abstract DocumentBase entity until you have at least one concrete Document type — without it, EF Core throws at migrations add time on a fresh scaffold, which has zero Document types by definition. Don't simplify this to an unconditional Ignore<DocumentBase>() — it's written to stop applying on its own once you add a real Document, and an unconditional version would silently break Document Links once you have one.

See also

  • How to start for the scaffolding commands that produce this project shape in the first place.