Benutzerdefinierte Migrationsvorgänge

Die MigrationBuilder-API ermöglicht es Ihnen, während einer Migration viele verschiedene Arten von Vorgängen auszuführen, aber sie ist bei weitem nicht vollständig. Die API ist jedoch auch erweiterbar, sodass Sie Ihre eigenen Vorgänge definieren können. Es gibt zwei Möglichkeiten zum Erweitern der API: Das Verwenden der Sql()-Methode oder das Definieren von benutzerdefinierten MigrationOperation-Objekten.

Zur Veranschaulichung sehen wir uns für jeden Ansatz die Implementierung eines Vorgangs an, der einen Datenbankbenutzer erstellt. In unseren Migrationen möchten wir das Schreiben des folgenden Codes ermöglichen:

migrationBuilder.CreateUser("SQLUser1", "Password");

Verwenden von MigrationBuilder.Sql()

Die einfachste Möglichkeit zum Implementieren eines benutzerdefinierten Vorgangs besteht darin, eine Erweiterungsmethode zu definieren, die MigrationBuilder.Sql() aufruft. Hier sehen Sie ein Beispiel, das die entsprechende Transact-SQL generiert.

public static OperationBuilder<SqlOperation> CreateUser(
    this MigrationBuilder migrationBuilder,
    string name,
    string password)
    => migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';");

Tipp

Verwenden Sie die EXEC-Funktion, wenn eine Anweisung die erste oder einzige in einem SQL-Batch sein muss. Es kann auch erforderlich sein, um Parserfehler in idempotenten Migrationsskripts zu umgehen, die auftreten können, wenn referenzierte Spalten in einer Tabelle derzeit nicht vorhanden sind.

Wenn Ihre Migrationen mehrere Datenbankanbieter unterstützen müssen, können Sie die MigrationBuilder.ActiveProvider-Eigenschaft verwenden. Hier ist ein Beispiel, das sowohl Microsoft SQL Server als auch PostgreSQL unterstützt.

public static OperationBuilder<SqlOperation> CreateUser(
    this MigrationBuilder migrationBuilder,
    string name,
    string password)
{
    switch (migrationBuilder.ActiveProvider)
    {
        case "Npgsql.EntityFrameworkCore.PostgreSQL":
            return migrationBuilder
                .Sql($"CREATE USER {name} WITH PASSWORD '{password}';");

        case "Microsoft.EntityFrameworkCore.SqlServer":
            return migrationBuilder
                .Sql($"CREATE USER {name} WITH PASSWORD = '{password}';");
    }

    throw new Exception("Unexpected provider.");
}

Dieser Ansatz funktioniert nur, wenn Sie jeden Anbieter kennen, auf den Ihr benutzerdefinierter Vorgang angewendet werden wird.

Verwenden einer MigrationOperation

Um den benutzerdefinierten Vorgang von SQL zu entkoppeln, können Sie Ihren eigenen MigrationOperation definieren, um ihn darzustellen. Der Vorgang wird dann an den Anbieter übergeben, sodass er die entsprechende zu generierende SQL ermitteln kann.

public class CreateUserOperation : MigrationOperation
{
    public string Name { get; set; }
    public string Password { get; set; }
}

Bei diesem Ansatz muss die Erweiterungsmethode nur einen dieser Vorgänge zu MigrationBuilder.Operations hinzufügen.

public static OperationBuilder<CreateUserOperation> CreateUser(
    this MigrationBuilder migrationBuilder,
    string name,
    string password)
{
    var operation = new CreateUserOperation { Name = name, Password = password };
    migrationBuilder.Operations.Add(operation);

    return new OperationBuilder<CreateUserOperation>(operation);
}

Bei diesem Ansatz muss jeder Anbieter wissen, wie er SQL für diesen Vorgang in seinem IMigrationsSqlGenerator-Dienst generiert. Hier ist ein Beispiel zum Überschreiben des SQL Server-Generators zum Verarbeiten des neuen Vorgangs.

public class MyMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
    public MyMigrationsSqlGenerator(
        MigrationsSqlGeneratorDependencies dependencies,
        ICommandBatchPreparer commandBatchPreparer)
        : base(dependencies, commandBatchPreparer)
    {
    }

    protected override void Generate(
        MigrationOperation operation,
        IModel model,
        MigrationCommandListBuilder builder)
    {
        if (operation is CreateUserOperation createUserOperation)
        {
            Generate(createUserOperation, builder);
        }
        else
        {
            base.Generate(operation, model, builder);
        }
    }

    private void Generate(
        CreateUserOperation operation,
        MigrationCommandListBuilder builder)
    {
        var sqlHelper = Dependencies.SqlGenerationHelper;
        var stringMapping = Dependencies.TypeMappingSource.FindMapping(typeof(string));

        builder
            .Append("CREATE USER ")
            .Append(sqlHelper.DelimitIdentifier(operation.Name))
            .Append(" WITH PASSWORD = ")
            .Append(stringMapping.GenerateSqlLiteral(operation.Password))
            .AppendLine(sqlHelper.StatementTerminator)
            .EndCommand();
    }
}

Ersetzen Sie den SQL-Generatordienst der Standardmigrationen durch den aktualisierten.

protected override void OnConfiguring(DbContextOptionsBuilder options)
    => options
        .UseSqlServer(_connectionString)
        .ReplaceService<IMigrationsSqlGenerator, MyMigrationsSqlGenerator>();