Operações de migrações personalizadasCustom Migrations Operations

A API MigrationBuilder permite que você execute muitos tipos diferentes de operações durante a migração, mas ele está longe de ser completa.The MigrationBuilder API allows you to perform many different kinds of operations during a migration, but it's far from exhaustive. No entanto, a API também é extensível, permitindo que você defina suas próprias operações.However, the API is also extensible allowing you to define your own operations. Há duas maneiras de estender a API: usando o Sql() método, ou definindo personalizado MigrationOperation objetos.There are two ways to extend the API: Using the Sql() method, or by defining custom MigrationOperation objects.

Para ilustrar, vamos dar uma olhada na implementação de uma operação que cria um usuário de banco de dados usando cada abordagem.To illustrate, let's look at implementing an operation that creates a database user using each approach. Em nosso migrações, queremos permitir escrever o código a seguir:In our migrations, we want to enable writing the following code:

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

Usando MigrationBuilder.Sql()Using MigrationBuilder.Sql()

A maneira mais fácil de implementar uma operação personalizada é definir um método de extensão que chama MigrationBuilder.Sql().The easiest way to implement a custom operation is to define an extension method that calls MigrationBuilder.Sql(). Aqui está um exemplo que gera o Transact-SQL apropriado.Here is an example that generates the appropriate Transact-SQL.

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

Se suas migrações precisam dar suporte a vários provedores de banco de dados, você pode usar o MigrationBuilder.ActiveProvider propriedade.If your migrations need to support multiple database providers, you can use the MigrationBuilder.ActiveProvider property. Aqui está um exemplo de suporte do Microsoft SQL Server e o PostgreSQL.Here's an example supporting both Microsoft SQL Server and PostgreSQL.

static MigrationBuilder 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}';");
    }

    return migrationBuilder;
}

Essa abordagem funciona somente se você souber que cada provedor de onde sua operação personalizada será aplicada.This approach only works if you know every provider where your custom operation will be applied.

Usando um MigrationOperationUsing a MigrationOperation

Para desacoplar a operação personalizada do SQL, você pode definir seus próprios MigrationOperation para representá-lo.To decouple the custom operation from the SQL, you can define your own MigrationOperation to represent it. A operação é então passada para o provedor para que possa determinar apropriados do SQL para gerar.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; }
}

Com essa abordagem, o método de extensão só precisa adicionar uma dessas operações para MigrationBuilder.Operations.With this approach, the extension method just needs to add one of these operations to MigrationBuilder.Operations.

static MigrationBuilder CreateUser(
    this MigrationBuilder migrationBuilder,
    string name,
    string password)
{
    migrationBuilder.Operations.Add(
        new CreateUserOperation
        {
            Name = name,
            Password = password
        });

    return migrationBuilder;
}

Essa abordagem requer que cada provedor de saber como gerar o SQL para essa operação em seus IMigrationsSqlGenerator service.This approach requires each provider to know how to generate SQL for this operation in their IMigrationsSqlGenerator service. Aqui está um exemplo, substituindo o gerador do SQL Server para lidar com a nova operação.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();
    }
}

Substitua o serviço gerador de sql de migrações de padrão com um atualizado.Replace the default migrations sql generator service with the updated one.

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