Database Upgrade Events

Summary

The database upgrade system uses an event-driven approach to handle schema migrations. When the application starts, it compares the current EF Core model against the live database schema. When differences are detected — such as new columns, removed columns, new tables, or removed tables — subscriber methods are invoked to run data migration scripts before the schema change completes.

This system also supports feature version upgrades, which are versioned data transformations that run independently of schema changes and are tracked in a __FeatureVersion table.

How It Works

On startup, the DatabaseUpgrader orchestrates the upgrade:

Lock database → Create temp schema from EF model → Compare schemas
→ Invoke migration subscribers → Apply schema changes → Feature version upgrades → Unlock
  1. The database is locked to prevent concurrent upgrades
  2. A temporary schema is generated from the current EF Core model
  3. The temp schema is compared against the live schema to detect changes
  4. For each detected change, Core runs the matching subscribers allowed for that change
  5. Subscribers emit SQL scripts via args.UpgradeScript(sql) to migrate data
  6. All scripts run in a single transaction
  7. Feature version upgrades run after schema migration

Event Reference

Schema Migration Events

Event Description
PropertyAdded Fires when a new column is added to an existing table. Use to populate the new column with initial data.
PropertyDeleted Fires when a column is removed from a table. Use to migrate data to other columns before removal.
EntityAdded Fires when a new table is created. Use to seed initial data or migrate data from other tables.
EntityDeleted Fires when a table is removed. Use to migrate data to replacement tables before removal.

Feature Dependencies

A schema subscriber may use a table or column from another feature. Set FeatureName on the subscriber attribute to name that feature.

[PropertyAdded(
    nameof(Product),
    "SalesAccountGuid",
    FeatureName = "Benevia.ERP.Model.Financials")]

Core skips this subscriber when the feature is off for the tenant. If FeatureName is empty, the subscriber runs as before. The subscriber class's namespace does not set FeatureName.

Feature Version Events

Event Description
FeatureVersionChanged Fires for versioned data upgrades that are independent of schema changes. Version state is tracked in the database.

Base Event Args

All migration event args inherit from MigrationEventArgs, which provides:

Method Description
UpgradeScript(string sql) Adds a SQL script to be executed after all subscribers have been invoked
ReadFromDatabase(string sql) Reads data from the current database to help generate migration scripts (read-only)

Safe and Dangerous Changes

Removed columns and tables are marked deleted in place — data and name stay, and the object is restored automatically if it returns to the model. Some changes (type changes, making columns required, moving data) need a manual two-release recipe. See Safe and Dangerous Schema Changes.

Notes

  • Subscribers are discovered automatically via reflection across all loaded assemblies.
  • Schema subscribers follow the tenant's EF Core model, not the subscriber class's namespace.
  • If a disabled feature removes a table or column from the tenant model, Core does not report it as added.
  • Core skips deletion subscribers when an object is only hidden by a disabled feature.
  • Core runs deletion subscribers when an object is permanently removed.
  • A disabled feature's version upgrade runs later when the feature is enabled.
  • Core uses the version subscriber class's namespace to find its feature.
  • Core runs every version subscriber if the host has no feature catalog or current tenant feature scope.
  • All migration scripts run within a single transaction — if any script fails, the entire upgrade is rolled back.
  • NOT NULL constraint changes are applied last, after all subscriber scripts have run.
  • Schema migration subscribers fire based on detected schema differences — they do not fire if the schema has not changed.
  • Feature version upgrades are tracked in the __FeatureVersion table and only run once per version. A database created from blank is recorded at each enabled feature's highest version, so historical upgrades never run on it.
  • Tenant tables live in public. For the rare tenant that needs another schema, see Custom Schema.