Compartir a través de


Operaciones de migraciones personalizadas

MigrationBuilder API permite realizar muchos tipos diferentes de operaciones durante una migración, pero está lejos de ser 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 MigrationOperation personalizados.

Para ilustrarlo, 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

En SQL Server, use la función EXEC cuando una instrucción debe ser la primera o solo una 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 separar la operación personalizada del SQL, puede definir su propio MigrationOperation para representarla. A continuación, la operación se pasa al proveedor para que pueda determinar el CÓDIGO 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. Aquí se presenta un ejemplo sobrescribiendo el generador de SQL Server para manejar 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>();