Operações de migrações personalizadas
A API MigrationBuilder permite que você execute muitos tipos diferentes de operações durante uma migração, mas está longe de ser exaustiva. No entanto, a API também é extensível, permitindo que você defina suas próprias operações. Há duas maneiras de estender a API: usando o método Sql()
ou definindo objetos MigrationOperation
personalizados.
Para ilustrar, vamos examinar a implementação de uma operação que cria um usuário de banco de dados usando cada abordagem. Em nossas migrações, queremos habilitar a gravação do seguinte código:
migrationBuilder.CreateUser("SQLUser1", "Password");
Usando MigrationBuilder.Sql()
A maneira mais fácil de implementar uma operação personalizada é definir um método de extensão que chama MigrationBuilder.Sql()
. Aqui está um exemplo que gera o Transact-SQL apropriado.
public static OperationBuilder<SqlOperation> CreateUser(
this MigrationBuilder migrationBuilder,
string name,
string password)
=> migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';");
Dica
Use a função EXEC
quando uma instrução deve ser a primeira ou a única em um lote de SQL. Também pode ser necessário contornar erros de analisador em scripts de migração idempotentes que podem ocorrer quando colunas referenciadas não existem atualmente em uma tabela.
Se suas migrações precisarem dar suporte a vários provedores de banco de dados, você poderá usar a propriedade MigrationBuilder.ActiveProvider
. Aqui está um exemplo que dá suporte ao Microsoft SQL Server e ao 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.");
}
Essa abordagem só funcionará se você souber todos os provedores em que sua operação personalizada será aplicada.
Usando um MigrationOperation
Para desacoplar a operação personalizada do SQL, você pode definir sua própria MigrationOperation
para representá-la. Em seguida, a operação é passada para o provedor para que possa determinar o SQL apropriado a ser gerado.
public 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 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);
}
Essa abordagem exige que cada provedor saiba como gerar SQL para essa operação em seu serviço de IMigrationsSqlGenerator
. Aqui está um exemplo substituindo o gerador do SQL Server para identificar a nova operação.
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();
}
}
Substitua o serviço de gerador SQL de migrações padrão pelo atualizado.
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options
.UseSqlServer(_connectionString)
.ReplaceService<IMigrationsSqlGenerator, MyMigrationsSqlGenerator>();