Benutzerdefinierte Migrations VorgängeCustom Migrations Operations

Die migrationbuilder-API ermöglicht es Ihnen, viele verschiedene Arten von Vorgängen während einer Migration auszuführen, aber es ist weit von der Vollständigkeit her.The MigrationBuilder API allows you to perform many different kinds of operations during a migration, but it's far from exhaustive. Die API ist jedoch auch erweiterbar, sodass Sie eigene Vorgänge definieren können.However, the API is also extensible allowing you to define your own operations. Es gibt zwei Möglichkeiten, die API zu erweitern: mithilfe der- Sql() Methode oder durch Definieren von benutzerdefinierten MigrationOperation Objekten.There are two ways to extend the API: Using the Sql() method, or by defining custom MigrationOperation objects.

Um dies zu veranschaulichen, betrachten wir die Implementierung eines Vorgangs, der einen Datenbankbenutzer mit jedem Ansatz erstellt.To illustrate, let's look at implementing an operation that creates a database user using each approach. In unseren Migrationen möchten wir das Schreiben des folgenden Codes aktivieren:In our migrations, we want to enable writing the following code:

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

Verwenden von migrationbuilder. SQL ()Using MigrationBuilder.Sql()

Die einfachste Möglichkeit zum Implementieren eines benutzerdefinierten Vorgangs besteht darin, eine Erweiterungsmethode zu definieren, die aufruft MigrationBuilder.Sql() .The easiest way to implement a custom operation is to define an extension method that calls MigrationBuilder.Sql(). Es folgt ein Beispiel, das die entsprechende Transact-SQL-Anwendung generiert.Here is an example that generates the appropriate Transact-SQL.

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 der erste oder einzige in einem SQL-Batch sein muss.Use the EXEC function when a statement must be the first or only one in a SQL batch. Es kann auch erforderlich sein, Parserfehler in idempotenten Migrations Skripts zu umgehen, die auftreten können, wenn Spalten, auf die verwiesen wird, derzeit nicht in einer Tabelle vorhanden sindIt might also be needed to work around parser errors in idempotent migration scripts that can occur when referenced columns don't currently exist on a table.

Wenn Ihre Migrationen mehrere Datenbankanbieter unterstützen müssen, können Sie die- MigrationBuilder.ActiveProvider Eigenschaft verwenden.If your migrations need to support multiple database providers, you can use the MigrationBuilder.ActiveProvider property. Hier sehen Sie ein Beispiel, das sowohl Microsoft SQL Server als auch PostgreSQL unterstützt.Here's an example supporting both Microsoft SQL Server and PostgreSQL.

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 der benutzerdefinierte Vorgang angewendet wird.This approach only works if you know every provider where your custom operation will be applied.

Verwenden von migrationoperationUsing a MigrationOperation

Um den benutzerdefinierten Vorgang von SQL zu entkoppeln, können Sie einen eigenen definieren, MigrationOperation um ihn zu repräsentieren.To decouple the custom operation from the SQL, you can define your own MigrationOperation to represent it. Der Vorgang wird dann an den Anbieter weitergegeben, damit er die zu generierende SQL-Datei bestimmen kann.The operation is then passed to the provider so it can determine the appropriate SQL to generate.

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

Bei diesem Ansatz muss die Erweiterungsmethode nur einen dieser Vorgänge hinzufügen MigrationBuilder.Operations .With this approach, the extension method just needs to add one of these operations to MigrationBuilder.Operations.

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);
}

Diese Vorgehensweise erfordert, dass jeder Anbieter weiß, wie SQL für diesen Vorgang in seinem Dienst generiert wird IMigrationsSqlGenerator .This approach requires each provider to know how to generate SQL for this operation in their IMigrationsSqlGenerator service. Hier ist ein Beispiel, das den Generator der SQL Server überschreibt, um den neuen Vorgang zu verarbeiten.Here is an example overriding the SQL Server's generator to handle the new operation.

class MyMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
    public MyMigrationsSqlGenerator(
        MigrationsSqlGeneratorDependencies dependencies,
        IMigrationsAnnotationProvider migrationsAnnotations)
        : base(dependencies, migrationsAnnotations)
    {
    }

    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-Generator-Standard Migrationsdienst durch den aktualisierten Dienst.Replace the default migrations sql generator service with the updated one.

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