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: mediante el Sql() método o mediante la definición de objetos MigrationOperation personalizados.
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 llame a MigrationBuilder.Sql() . Este es un ejemplo que genera la instrucción Transact-SQL.
private static OperationBuilder<SqlOperation> CreateUser(
this MigrationBuilder migrationBuilder,
string name,
string password)
=> migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';");
Sugerencia
Use la EXEC función cuando una instrucción debe ser la primera o la única de un SQL lote. También podría ser necesario evitar 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 MigrationBuilder.ActiveProvider propiedad . Este es un ejemplo de compatibilidad con Microsoft SQL Server y PostgreSQL.
private 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 a los que se aplicará la operación personalizada.
Uso de una clase MigrationOperation
Para desacoplar la operación personalizada de la SQL, puede definir la suya MigrationOperation propia para representarla. A continuación, la operación se pasa al proveedor para que pueda determinar la SQL que se va a generar.
internal 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 .
private 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 IMigrationsSqlGenerator servicio. Este es un ejemplo en el que se reemplaza SQL Server generador de la aplicación para controlar la nueva operación.
internal class MyMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
public MyMigrationsSqlGenerator(
MigrationsSqlGeneratorDependencies dependencies,
IRelationalAnnotationProvider 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();
}
}
Reemplace el servicio de generador de SQL de migraciones predeterminadas por el actualizado.
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options
.UseSqlServer(_connectionString)
.ReplaceService<IMigrationsSqlGenerator, MyMigrationsSqlGenerator>();