PouryaKit
Guides

Add a migration

Change a Module's schema with dotnet-ef, from its own migrations folder, and apply it before the Host starts.

What you build

An EF Core migration in the Module's own migrations folder, for the Module's own schema. Each Module keeps its migrations apart, with its own history table in its schema. The DbContext stays internal and needs no design-time factory: dotnet-ef builds the Host as its Program does and finds the DbContext in its container.

The Host applies no migration when it starts

Apply migrations yourself, before the Host runs, as part of your deployment. In tests, HostFixture applies every Module's migrations before it starts the Host.

When to use it

Whenever a Module's model changes: a new aggregate or property, opting an aggregate into audit, soft delete or a tenant, or a new unique index. Also once for each table the Kit keeps outside the model, such as the Receipt table or the idempotency key table.

Steps

Restore the tools

The solution's tool manifest holds dotnet-ef. From the solution's folder:

Terminal
dotnet tool restore

Add the migration

From the folder that holds src/, name the Module's project, the Host as the startup project, the DbContext and the output folder. These are the Proving Sample's paths:

Terminal
dotnet ef migrations add Initial \
  --project src/Modules/Catalog/Catalog \
  --startup-project src/ModularMonolith.Host \
  --context CatalogDbContext \
  --output-dir Infrastructure/Data/Migrations

Fill a migration for a table outside the model

The Receipt table, the Snapshot version table and the idempotency key table are not in the Module's model, so their migration is empty and the model snapshot stays as it was. Fill its Up and Down with the Kit's call and the Module's schema:

20261005180000_Receipts.cs
// The Receipts of Catalog's Notification Handlers. The table is not in the model, so it leaves the snapshot unchanged.
[DbContext(typeof(CatalogDbContext))]
[Migration("20261005180000_Receipts")]
internal sealed class Receipts : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder) => migrationBuilder.CreateReceiptTable("catalog");

    protected override void Down(MigrationBuilder migrationBuilder) => migrationBuilder.DropReceiptTable("catalog");
}

Apply it

In a solution generated from a template, apply each Module's migrations to the database the Host's connection string names. A missing database is created before the first migration:

Terminal
dotnet ef database update --project src/Modules/Catalog/Catalog --startup-project src/Acme.Shop.Host --context CatalogDbContext

The modular monolith also migrates Billing and Inventory the same way, and Ledger, a Stream Module, needs none.

What the Kit guarantees

GuaranteeStated in
How a Module's migrations are addedMigrations
Which Module needs the Receipt and Snapshot version tablesReceipts
The idempotency key tableMigrations
Running and migrating a generated HostRunning the generated Host

Failure messages

Next steps

On this page