Operazioni di migrazione personalizzate
L'API MigrationBuilder consente di eseguire molti tipi diversi di operazioni durante una migrazione, ma non è esaustivo. Tuttavia, l'API è anche estendibile che consente di definire le proprie operazioni. Esistono due modi per estendere l'API: l'uso del Sql()
metodo o la definizione di oggetti personalizzati MigrationOperation
.
Per illustrare l'implementazione di un'operazione che crea un utente di database usando ogni approccio. Nelle migrazioni si vuole abilitare la scrittura del codice seguente:
migrationBuilder.CreateUser("SQLUser1", "Password");
Uso di MigrationBuilder.Sql()
Il modo più semplice per implementare un'operazione personalizzata consiste nel definire un metodo di estensione che chiama MigrationBuilder.Sql()
. Di seguito è riportato un esempio che genera l'istruzione Transact-SQL appropriata.
public static OperationBuilder<SqlOperation> CreateUser(
this MigrationBuilder migrationBuilder,
string name,
string password)
=> migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';");
Suggerimento
Usare la EXEC
funzione quando un'istruzione deve essere la prima o una sola in un batch SQL. Potrebbe anche essere necessario risolvere gli errori del parser negli script di migrazione idempotenti che possono verificarsi quando le colonne a cui si fa riferimento non esistono attualmente in una tabella.
Se le migrazioni devono supportare più provider di database, è possibile usare la MigrationBuilder.ActiveProvider
proprietà . Di seguito è riportato un esempio che supporta sia Microsoft SQL Server che 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.");
}
Questo approccio funziona solo se si conosce ogni provider in cui verrà applicata l'operazione personalizzata.
Uso di MigrationOperation
Per separare l'operazione personalizzata da SQL, è possibile definirla per MigrationOperation
rappresentarla. L'operazione viene quindi passata al provider in modo che possa determinare il codice SQL appropriato da generare.
public class CreateUserOperation : MigrationOperation
{
public string Name { get; set; }
public string Password { get; set; }
}
Con questo approccio, il metodo di estensione deve solo aggiungere una di queste operazioni 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);
}
Questo approccio richiede a ogni provider di sapere come generare SQL per questa operazione nel servizio IMigrationsSqlGenerator
. Di seguito è riportato un esempio di override del generatore di SQL Server per gestire la nuova operazione.
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();
}
}
Sostituire il servizio generatore sql delle migrazioni predefinito con quello aggiornato.
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options
.UseSqlServer(_connectionString)
.ReplaceService<IMigrationsSqlGenerator, MyMigrationsSqlGenerator>();