Versioning and scoped history
Number changes, keep applied source immutable and give independent modules explicit histories.
Examples use Classic and Fluent tabs. Your choice follows you through the manual.
Choose a version scheme
Migration accepts a numeric version or year/month/day/hour/minute/second components. Use one monotonic scheme per migration set. The date constructor builds a numeric identifier; it does not consult a clock or resolve branch collisions for you. Missing lower-numbered versions up to the target can still be applied.
Classic
using System;
using System.Data;
using DotNetProjects.Migrator;
using DotNetProjects.Migrator.Framework;
[Migration(2026, 9, 23, 10, 0, 0)]
public class AddUserEmail : Migration
{
public override void Up() => Database.AddColumn("Users", new Column("Email", DbType.String, 320));
public override void Down() => Database.RemoveColumn("Users", "Email");
}Fluent
using System;
using System.Data;
using DotNetProjects.Migrator;
using DotNetProjects.Migrator.Framework;
using DotNetProjects.Migrator.Framework.Fluent;
[Migration(2026, 9, 23, 10, 0, 0)]
public class AddUserEmail : FluentMigration
{
public override void BuildUp(MigrationBuilder migration)
=> migration.Create.Column("Email").OnTable("Users").AsString(320);
public override void BuildDown(MigrationBuilder migration)
=> migration.Delete.Column("Email").FromTable("Users");
}Choose one authoring style
Scope selection
An explicit MigrationAttribute.Scope selects that migration only for the matching provider scope. Unscoped migrations inherit the runner scope. Discovery, duplicate validation and history reads use the effective scope. Duplicate numeric versions in distinct explicit scopes are independent; physical tables are not isolated.
Set SchemaInfoTableName before any history access if you need a different table. AppliedMigrations lists recorded versions; LastAppliedMigrationVersion is nullable when history is empty. AssemblyLastMigrationVersion describes the loaded set.
Consolidated baselines
A baseline can record versions whose schema it already includes. The runner rechecks active-scope history before each planned step, skipping newly covered versions and their AfterUp callbacks. Downward runs similarly skip versions removed by an earlier Down. Recording the baseline version itself does not create a duplicate.
Classic
Database.MigrationApplied(1, "billing");Fluent
migration.Execute.WithProvider(provider => provider.MigrationApplied(1, "billing"));Inside Up() / BuildUp(MigrationBuilder migration)
Only record a version after establishing the schema it represents. History entries are not a substitute for verifying an existing database. Schema/history rollback follows the selected transaction mode. No migration-content checksum is stored.