Operaciones de migraciones personalizadas

La API MigrationBuilder permite realizar muchos tipos diferentes de operaciones durante una migración, pero no es exhaustiva. Sin embargo, la API también es extensible, lo que le permite definir sus propias operaciones. Hay dos maneras de ampliar la API: usar el método Sql() o definir objetos personalizados MigrationOperation.

Para ilustrar esto, echemos un vistazo a la implementación de una operación que crea un usuario de base de datos mediante cada enfoque. En nuestras migraciones, queremos habilitar la escritura del código siguiente:

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

Uso de MigrationBuilder.Sql()

La manera más fácil de implementar una operación personalizada es definir un método de extensión que llama a MigrationBuilder.Sql(). Este es un ejemplo que genera el Transact-SQL adecuado.

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

Sugerencia

Use la función EXEC cuando una instrucción debe ser la primera o la única en un lote de SQL. También puede ser necesario solucionar errores del analizador en scripts de migración idempotentes que pueden producirse cuando las columnas a las que se hace referencia no existen actualmente en una tabla.

Si las migraciones necesitan admitir varios proveedores de bases de datos, puede usar la propiedad MigrationBuilder.ActiveProvider. Este es un ejemplo que admite Microsoft SQL Server y PostgreSQL.

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

Este enfoque solo funciona si conoce todos los proveedores en los que se aplicará la operación personalizada.

Uso de MigrationOperation

Para desacoplar la operación personalizada de SQL, puede definir su a propia MigrationOperation para representarla. A continuación, la operación se pasa al proveedor para que pueda determinar el SQL adecuado que se va a generar.

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

Con este enfoque, el método de extensión solo necesita agregar una de estas operaciones a MigrationBuilder.Operations.

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

Este enfoque requiere que cada proveedor sepa cómo generar SQL para esta operación en su servicio IMigrationsSqlGenerator. Este es un ejemplo que invalida el generador de SQL Server para controlar la nueva operación.

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

Reemplace el servicio del generador de SQL de migraciones predeterminado por el actualizado.

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