Your first migration
Create a SQLite database, apply a versioned change, and write its reverse. Choose either C# style; the runner is the same.
Examples use Classic and Fluent tabs. Your choice follows you through the manual.
Install the library
Start with the .NET 9 SDK and a console application. The core package supplies schema operations; your ADO.NET package connects to the database. SQLite needs no separate database server for this example.
Classic
dotnet new console -n MigrationDemo -f net9.0
cd MigrationDemo
dotnet add package DotNetProjects.Migrator
dotnet add package Microsoft.Data.Sqlite --version 9.0.7Fluent
dotnet new console -n MigrationDemo -f net9.0
cd MigrationDemo
dotnet add package DotNetProjects.Migrator
dotnet add package Microsoft.Data.Sqlite --version 9.0.7Shared commands · both styles
Write the change
Add CreateUsers.cs. Choose one tab and copy that class. Each public migration has a numeric version; do not put both versions of the same example into one assembly. Classic migrations execute provider methods in Up and Down. Fluent migrations collect structured operations in BuildUp and BuildDown.
Classic
using System.Data;
using DotNetProjects.Migrator.Framework;
[Migration(1)]
public class CreateUsers : Migration
{
public override void Up()
{
Database.AddTable("Users",
new Column("Id", DbType.Int32) { IsNullable = false },
new Column("Name", DbType.String, 255),
new PrimaryKeyConstraint("PK_Users", "Id"));
}
public override void Down() => Database.RemoveTable("Users");
}Fluent
using DotNetProjects.Migrator.Framework;
using DotNetProjects.Migrator.Framework.Fluent;
[Migration(1)]
public class CreateUsers : FluentMigration
{
public override void BuildUp(MigrationBuilder migration)
{
migration.Create.Table("Users")
.WithColumn("Id").AsInt32().NotNullable()
.WithColumn("Name").AsString(255)
.WithPrimaryKey("PK_Users", "Id");
}
public override void BuildDown(MigrationBuilder migration)
=> migration.Delete.Table("Users");
}Choose one authoring style
Run it
Replace Program.cs with the shared host below and run dotnet run. The open connection belongs to this host and is disposed after the provider. The runner discovers public migration classes in the selected assembly.
Classic
using DotNetProjects.Migrator;
using DotNetProjects.Migrator.Providers;
using Microsoft.Data.Sqlite;
using var connection = new SqliteConnection("Data Source=app.db");
connection.Open();
using var provider = ProviderFactory.Create(
ProviderTypes.SQLite, connection, defaultSchema: null);
var runner = new Migrator(provider, typeof(CreateUsers).Assembly, trace: false);
runner.MigrateToLastVersion();Fluent
using DotNetProjects.Migrator;
using DotNetProjects.Migrator.Providers;
using Microsoft.Data.Sqlite;
using var connection = new SqliteConnection("Data Source=app.db");
connection.Open();
using var provider = ProviderFactory.Create(
ProviderTypes.SQLite, connection, defaultSchema: null);
var runner = new Migrator(provider, typeof(CreateUsers).Assembly, trace: false);
runner.MigrateToLastVersion();Shared host · both styles
The result is an app.db file containing Users and SchemaInfo. Run again: version 1 is already recorded, so it is skipped. Add a class with [Migration(2)] for your next change.
Reverse a change
Call runner.MigrateTo(0) to execute the reverse methods for this migration set. Here that drops Users and its data. A reverse migration is a schema operation, not a restore of deleted rows. Test both directions on a disposable database before deployment.
Continue with creating tables, or configure scopes, filters and transaction behavior.