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