Database — migrations
What it is
ManpowerIQ's schema is managed by EF Core 8 migrations — 78 migration files under ManpowerIQ.Infrastructure/Migrations/, with the ModelSnapshot as the canonical current-state. Migrations are the schema source of truth; there is no separate hand-maintained DDL.
How it works
- Applied explicitly, not on startup. Migrations are run with
dotnet ef database update(against the owner connection); the API does not migrate-on-startup (corrected in Phase 3 against the runbook). The applied set is tracked in__EFMigrationsHistory.
dotnet ef database update --project src/ManpowerIQ.Infrastructure --startup-project src/ManpowerIQ.API
- Two connection strings. Migrations use the owner connection (
manpoweriq,BYPASSRLS); runtime uses the RLS-boundmanpoweriq_app. Runtime can never bypass tenant isolation (see Multi-tenancy). - snake_case by convention —
.UseSnakeCaseNamingConvention(), so C#BusinessUnitId→business_unit_id. - RLS lives in migrations — 12 migrations run raw
ENABLE/FORCE ROW LEVEL SECURITY+CREATE POLICY tenant_isolation(selective allow-list of tables; see Multi-tenancy). RowVersiondefaults —bytearowversion columns need a Postgres-side default'\x'::bytea; a sweep of "RowVersionDefault" migrations exists because older insert paths failed without it (sheet 01 edge-cases).- Hangfire schema is installed separately by a bootstrap gate (
HangfireSchemaBootstrap,PrepareSchemaIfNecessary), not by these EF migrations — its 12 tables live in ahangfireschema.
The reset reality — drop + recreate
Seed-data migrations are not idempotent against a truncation-style reset (PB-025). Several migrations INSERT seed/reference rows; truncating tables (e.g. a Respawn reset) leaves __EFMigrationsHistory saying the seed ran while the rows are gone, so the seed does not re-insert. The supported reset is therefore drop + recreate, not truncate (runbook §4a / §11):
dotnet ef database drop -f --project src/ManpowerIQ.Infrastructure --startup-project src/ManpowerIQ.API
dotnet ef database update --project src/ManpowerIQ.Infrastructure --startup-project src/ManpowerIQ.API
A telltale of the broken state: POST /api/auth/login-dev returns 401 Unknown user and §4b row counts read zero — the demo-user seed is missing because the schema was truncated, not dropped (runbook §4a warning).
Gotchas / constraints
- Don't rely on truncation to reset — use drop + recreate, or the seed baseline won't be restored (PB-025).
- No migrate-on-startup — apply migrations explicitly before running the API; the app assumes the schema is already at HEAD.
- Down-migrations exist but the reset path is drop+recreate — treat
Down()as the structural inverse for review, not the day-to-day reset mechanism. __EFMigrationsHistoryis the authority on what's applied — a mismatch between it and actual rows is the PB-025 signature.- Hangfire schema corruption is recovered manually (
DROP SCHEMA hangfire CASCADE;→ the gate reinstalls), intentionally not automated so destructive ops stay operator-explicit (runbook §5).
Build status
Available — 78 migrations applied via EF; the drop+recreate reset is the documented local workflow.
Related
- Schema overview · Seeds & reference data
- Multi-tenancy — the two-connection split + RLS migrations.
- Local run & reset — the operator-facing reset steps.
- Source:
Migrations/(78 files) + ModelSnapshot; runbookLocal_Environment_Runbook.md§4a/§11; PB-025.