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 Genie:Migration config section

Section titled “Controlling it — the Genie:Migration config section”

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

"Genie": {
"Migration": {
"MigrationExecution": "Forced",
"ModelsPath": "models",
"FilePatterns": [ "*.entity.xml", "*.view.xml", "*.sql", "*.navbar.xml" ]
}
}

FilePatterns chooses which kinds of file the sweep picks up; the stage order is fixed regardless (entities → views → SQL → navbar → RBAC), because a view may reference an entity and a grant may reference a view. Configuring it replaces the default, and an unsupported pattern is rejected at startup rather than collected and never run.

MigrationExecution:

  • 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.MigrationExecution = MigrationExecutionMode.Yes));

Point Genie:Migration:ModelsPath at your model files:

  • An absolute path is used as-is.
  • A relative path is resolved against the application base directory (bin/…) first, then the host content root — so "models" covers both a published app and running from the source tree.
  • A configured path that does not exist is logged as an error and migration is skipped. It deliberately does not fall back to the conventional locations: a typo that silently migrates some other folder — or nothing at all — is worse than a visible stop.

Leave ModelsPath unset to use the convention: <appBase>/models, then <appBase>/../models. If neither exists the service logs a warning naming both probed paths and skips.

The simplest portable approach is to copy your model files into the build output. The Inventory sample copies ../models/** into bin/.../models with a <None … CopyToOutputDirectory="PreserveNewest"> item in its csproj, and sets "ModelsPath": "models":

<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. When you upgrade the engine and its model gains tables (e.g. Identity.ResourceCapabilities + Identity.PermissionCapabilities for capabilities and grants), scaffold a new host migration (dotnet ef migrations add <Name>) so those tables exist — this works for SQL Server and PostgreSQL from the same model.
  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.