Skip to content

Model migration

At startup the engine’s ModelMigrationService (an IHostedService registered by AddGenie) scans a models directory and applies each file in dependency order — entities → views → SQL → navbar — via a per-type strategy. This is what populates the runtime metadata stores the API reads from; it is separate from the EF migration that creates physical tables.

┌─▶ *.entity.xml ──[EntityMigrationStrategy]─────────▶ Entity store + triggers
ModelMigrationService ─────┼─▶ *.view.xml ────[ObjectViewMigrationStrategy]─────▶ ViewStore (ObjectView JSON)
scans models directory ├─▶ *.sql ─────────[StoredProcedureMigrationStrategy]─▶ Recognized DDL executed
└─▶ *.navbar.xml ──[NavbarMigrationStrategy]──────────▶ Navigation store
Prefer a picture?
File Strategy Result
*.entity.xml EntityMigrationStrategy Upserted into the entity store (entity metadata); the per-entity sequence/search/audit triggers are (re)created here.
*.view.xml ObjectViewMigrationStrategy Upserted into ViewStore — the polymorphic ObjectView JSON the UI renders as grid/form/view.
*.sql StoredProcedureMigrationStrategy Recognized DDL (CREATE [OR ALTER] PROCEDURE/FUNCTION/VIEW/TRIGGER) is executed; plain SQL is only recorded.
*.navbar.xml NavbarMigrationStrategy Synced into the navigation store.

Each applied file is recorded by a content hash, so an unchanged file is skipped on the next boot — only changed files re-run.

Controlling it — the Startup config section

Section titled “Controlling it — the Startup config section”

Model-migration behaviour binds from the Startup section (ModelMigrationOptions), overridable in code via ConfigureMigrations:

"Startup": { "ExecuteMigration": "Forced" }
  • No — don’t run on startup.
  • Yes — run, skipping files whose content hash is unchanged.
  • Forced — re-apply every file regardless of hash.
builder.Services.AddGenie<YourContext>(genie => genie
.LoadFromConfiguration(builder.Configuration)
.ConfigureMigrations(m => m.ExecuteMigration = ExecuteMigration.Yes));

The service resolves the directory relative to the app base directory, probing <appBase>/../../../../../models then <appBase>/../models. The simplest portable approach is to copy your model files into the build output and let the engine find them there. The Inventory sample copies ../models/** into bin/.../models with a <None … CopyToOutputDirectory="PreserveNewest"> item in its csproj:

<ItemGroup>
<None Include="..\models\**\*.entity.xml;..\models\**\*.view.xml;..\models\**\*.sql;..\models\**\*.navbar.xml"
Link="models\%(RecursiveDir)%(Filename)%(Extension)"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Match the directory layout to your deployment.

Two distinct steps run against the same database, in a fixed order at boot:

  1. dotnet ef migrations (design-time, in the host assembly) — creates the physical tables from the source-generated EF configurations. Applied by the host, typically via context.Database.Migrate() on startup.
  2. Engine SQL bootstrap (EngineSqlBootstrapper) — deploys the engine’s shared SQL objects (sequence functions, company-scope helpers, password-encryption procs). Runs before the model migration. See Backend integration.
  3. Model migration (ModelMigrationService) — populates the metadata stores and creates the per-entity triggers described above.
┌──────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│ dotnet ef migrations │ ─▶ │ EngineSqlBootstrapper │ ─▶ │ ModelMigrationService │
│ physical tables │ │ shared SQL objects │ │ metadata + triggers │
└──────────────────────┘ └───────────────────────┘ └───────────────────────┘
Prefer a picture?

So authoring a new entity is: write the *.entity.xml, dotnet ef migrations add (tables), then let the runtime migration register its metadata + triggers on the next boot.