Skip to content

Database migrations and warmup order

The invariant: only Raklet.Backend migrates the databases

Raklet.Backend's OWIN Startup.Configuration is the only place that applies EF migrations. It runs MigrateDatabaseToLatestVersion and an eager Database.Initialize(true) for each context, in this order:

  1. RakletEmailContext (RakletEmail DB)
  2. RakletSmsContext (RakletSms DB)
  3. RakletDb (RakletV3 DB)

Every other web app — Application (v3 portal), Raklet.Login, Raklet.Admin, Raklet.Api — calls Database.SetInitializer<RakletDb>(null) in its own Startup and never migrates. They assume the schema is already current.

Consequence: until backend has started and completed migration against an environment's databases, the schema lags the compiled EF model. Any app that then writes through EF fails on the missing columns.

Failure signature

A write fails with EF6's generic message:

An error occurred while executing the command definition. See the inner exception for details.

The inner exception is almost always Invalid column name '<X>'. It means the compiled model expects a column/table that a pending migration would create but that hasn't been applied. Example seen in the wild: organisation creation on login.raklet.org threw this because Announcements.AnnouncementType (added by Models/Migrations/...AddPools) was missing — RakletDb was 3 migrations behind.

Why warmup must hit backend first

Because backend is the sole migrator, a warmup that recycles the schema-dependent apps (v3/login/admin/api/crm) before backend migrates will serve new code against an un-migrated DB → the error above. So both warmup scripts warm backend first and abort if it can't start/migrate, instead of soft-warning and warming the rest:

  • scripts/dev/pin-canonical-hosts.ps1 — canonical *.raklet.org IIS sites.
  • scripts/dev/create-branch-preview.ps1 — per-branch preview hosts.

The Azure deploy warmup (Deployment.Scripts/WarmUpTest.ps1 / WarmUpDev.ps1) already lists backend first for the same reason.

If backend itself 500s on warmup, fix that first — nothing downstream works until it can migrate. Do not "warm around" it.

Diagnosing a schema gap

Compare each context's applied migrations against the migration classes in the code. For RakletDb (catalog RakletV3):

SELECT TOP 5 MigrationId
FROM dbo.__MigrationHistory
WHERE ContextKey LIKE '%Models.Migrations.Configuration%'
ORDER BY MigrationId DESC;

Compare the newest MigrationId to the newest file under Models/Migrations/ (RakletDb), Raklet.Email/Migrations/ (RakletEmail), or Raklet.Sms/Migrations/ (RakletSms). Any code migration newer than the newest history row is pending.

When backend is wedged on an earlier context

backend migrates RakletEmail → RakletSms → RakletDb in order, so a failure on an earlier context blocks every later one. A common wedge is:

There is already an object named '<Table>' in the database.

This happens when the table exists but has no __MigrationHistory row for the migration that creates it — so the migrator tries to CreateTable over an existing table and throws. Tests that create tables out-of-band against a shared DB are a typical source.

Reconciling an "already exists" migration (non-destructive)

If the existing table already matches the migration's definition, mark the migration applied instead of dropping data:

  1. Verify the live table matches the migration's columns, PK, indexes, and FKs (sys.columns / sys.indexes / sys.foreign_keys). If it does not match, stop — fix the schema first.
  2. Insert the missing history row so EF skips the CreateTable. The Model blob is the migration's own Target resource from its *.Designer .resx (base64 → varbinary); ContextKey and ProductVersion come from an existing row in that DB's __MigrationHistory. Run with SET QUOTED_IDENTIFIER ON (the XML value() decode requires it).
  3. Recycle the backend app pool and warm https://backend.raklet.org/ so Startup re-runs and continues to the later contexts (RakletSms, RakletDb).
  4. Re-check __MigrationHistory and the previously-missing columns/tables.

Dropping and recreating the table also works but loses any rows; prefer the history-row reconcile when the data matters.

  • docs/agents/iis-worktree-branch-previews.md — the preview workflow whose warmup this ordering protects.