⚠️ Preview Release: RoomSharp is currently in Preview. Learn more →

Migrations

Handcrafted and auto-generated schema evolution with safe fallbacks.

Overview

RoomSharp keeps schema changes aligned with your RoomDatabase version. The builder wires both handcrafted manual migrations and declarative auto migrations generated from attributes on your database type.

  • Manual migrations let you run any SQL or complex data fixes.
  • Auto migrations use attributes like [AutoMigration], [RenameTable], and [ColumnRename] to emit SQL for common changes.
  • Fallback destructive mode can drop and recreate tables for dev/test downgrades.

Metadata table

RoomSharp tracks schema state in __room_metadata. MigrationManager applies pending migrations up to the [Database(Version = N)] declared on your database implementation.

Registering migrations

// Database declaration with version
[Database(Version = 3, Entities = new[] { typeof(User) })]
public abstract class AppDatabase : RoomDatabase
{
    protected AppDatabase(IDatabaseProvider provider, ILogger? logger = null)
        : base(provider, logger) { }
}

// Builder wiring (add this in your composition root)
var db = RoomDatabase.Builder()
    .UseSqlite(\"app.db\")
    .AddMigrations(new UserMigration_1_2(), new UserMigration_2_3())
    .Build();

Manual migrations

Create a class that inherits Migration, then implement Migrate. You can run any SQL and execute multiple statements per step.

public sealed class UserMigration_1_2() : Migration(1, 2)
{
    public override void Migrate(DbConnection connection)
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = \"ALTER TABLE users ADD COLUMN status TEXT DEFAULT 'active'\";
        cmd.ExecuteNonQuery();
    }
}

Add the migration to the builder via AddMigrations. Migrations are executed in order and must form a continuous chain (1→2, 2→3, ...).

Auto migrations

Mark your database with [AutoMigration] attributes for version jumps. You can include an optional IAutoMigrationSpec for declarative tweaks and post-migrate hooks.

[Database(Version = 3, Entities = new[] { typeof(User) })]
[AutoMigration(From = 1, To = 2, Spec = typeof(UserTableSpec))]
[AutoMigration(From = 2, To = 3)]
public abstract class UserDatabase : RoomDatabase
{
    protected UserDatabase(IDatabaseProvider provider, ILogger? logger = null) : base(provider, logger) { }
}

[RenameTable(FromTableName = \"old_users\", ToTableName = \"users\")]
[DeleteColumn(TableName = \"users\", ColumnName = \"legacy_flag\")]
public sealed class UserTableSpec : IAutoMigrationSpec
{
    [ColumnRename(TableName = \"users\", FromColumnName = \"full_name\", ToColumnName = \"name\")]
    public string UsersTable => \"users\";

    public void OnPostMigrate(SqliteConnection connection)
    {
        using var cmd = connection.CreateCommand();
        cmd.CommandText = \"UPDATE users SET status='active' WHERE status IS NULL\";
        cmd.ExecuteNonQuery();
    }
}

Auto migrations are emitted at build time and executed before manual migrations that start at the same version.

Fallback to destructive migration

For local development or test environments you can allow destructive downgrades if no matching migration chain exists.

var db = RoomDatabase.Builder()
    .UseSqlite(\"app.db\")
    .FallbackToDestructiveMigration()
    .Build();

When enabled, RoomSharp drops user tables and recreates them from entity metadata, then resets __room_metadata. Use cautiously and avoid in production.

Callbacks and diagnostics

  • MigrationManager.ApplyMigrations logs each migration step; provide an ILogger to see details.
  • RoomDatabase.OnCreate and OnMigrate callbacks receive a flag indicating if a destructive migration occurred.
  • You can query db.GetCurrentVersion() to inspect the stored schema version.
Tips:
  • Keep versions contiguous—no gaps between StartVersion and EndVersion.
  • Run destructive fallback only in non-production environments.
  • Use auto migrations for schema shape changes, manual migrations for data backfills or complex SQL.