This article will show you how to create migrations, using Simple.Migrations, that can be run differently, in different environments, with parameters. This is kind of a strange requirement. And, to tell the truth, I figured out how to do this whilst trying out a “bad idea” and just ended up re-designing to avoid doing this. But if you do need to do this, here’s how.

So here’s a typical migration:

[Migration(1, "Do Something")]
public class CreateSomeStuff : Migration
{
    protected override void Up()
    {
        Execute("SOME SQL TO create.stuff");
    }

    protected override void Down()
    {
        Execute("SOME OTHER SQL to destroy.stuff");
    }
}

But what if we want to change behaviour at runtime? The natural thing is to add something to the constructor. Like this:

...
public class CreateSomeStuff : Migration
{
    public CreateSomeStuff(SomeMigrationParameter parameter)
    {
        _parameter = parameter;
    }
    ...
}

But this doesn’t work. Simple.Migrations doesn’t know how to get this value for you. It will throw and remind you that migrations must have parameterless constructors.

But we can change this. Extend the SimpleMigrator class like this:

public class ParameterizedSimpleMigrator<TMigrationParameter> : SimpleMigrator<DbConnection, IMigration<DbConnection>>
{
    private readonly TMigrationParameter _parameter;

    public ParameterizedSimpleMigrator(IMigrationProvider migrationProvider, IDatabaseProvider<DbConnection> databaseProvider,
        TMigrationParameter migrationParameter, ILogger logger = null) : base(migrationProvider, databaseProvider,
        logger)
    {
        _parameter = migrationParameter;
    }

    public ParameterizedSimpleMigrator(Assembly migrationsAssembly, IDatabaseProvider<DbConnection> databaseProvider,
        TMigrationParameter migrationParameter, ILogger logger = null) : base(migrationsAssembly,
        databaseProvider, logger)
    {
        _parameter = migrationParameter;
    }

    protected override IMigration<DbConnection> CreateMigration(MigrationData migrationData)
    {
        if (migrationData == null)
            throw new ArgumentNullException(nameof(migrationData));

        IMigration<DbConnection> instance;
        try
        {
            instance = (IMigration<DbConnection>)
                Activator.CreateInstance(migrationData.TypeInfo.AsType(), args: new object[] { _parameter });
        }
        catch (Exception e)
        {
            throw new MigrationException($"Unable to create migration {migrationData.FullName}", e);
        }

        return instance;
    }
}

This inherits from SimpleMigrator<DbConnection, IMigration<DbConnection>> and is almost entirely identical to the implementation of the vanilla SimpleMigrator. The only difference is that when it creates the instance of your migrations, it will pass them the instance TMigrationParameter. It will pass this TMigrationParameter to all of them.

If this is the thing you need, just replace your use of the SimpleMigrator class with this parameterized alternative, and you can start passing what you need into those migration constructors.