Share via


Opérations personnalisées de Migrations

L’API MigrationBuilder vous permet d’effectuer de nombreux types d’opérations différents lors d’une migration, mais elle est loin d’être exhaustive. Toutefois, l’API est également extensible, ce qui vous permet de définir vos propres opérations. Il existe deux façons d’étendre l’API : en utilisant la méthode Sql() ou en définissant des objets MigrationOperation personnalisés.

Pour illustrer cela, examinons l’implémentation d’une opération qui crée un utilisateur de base de données à l’aide de chacune de ces approches. Dans nos migrations, nous cherchons à activer l’écriture du code suivant :

migrationBuilder.CreateUser("SQLUser1", "Password");

Utilisation de MigrationBuilder.Sql()

Le moyen le plus simple d’implémenter une opération personnalisée consiste à définir une méthode d’extension qui appelle MigrationBuilder.Sql(). Voici un exemple qui génère le Transact-SQL approprié.

public static OperationBuilder<SqlOperation> CreateUser(
    this MigrationBuilder migrationBuilder,
    string name,
    string password)
    => migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';");

Conseil

Utilisez la fonction EXEC lorsqu’une instruction doit être la première ou une seule dans un lot SQL. Il peut également être nécessaire de contourner les erreurs d’analyseur dans les scripts de migration idempotents qui peuvent se produire lorsque des colonnes référencées n’existent pas actuellement sur une table.

Si vos migrations doivent prendre en charge plusieurs fournisseurs de base de données, vous pouvez utiliser la propriété MigrationBuilder.ActiveProvider. Voici un exemple prenant en charge Microsoft SQL Server et 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.");
}

Cette approche fonctionne uniquement si vous connaissez chaque fournisseur où votre opération personnalisée sera appliquée.

Utiliser MigrationOperation

Pour dissocier l’opération personnalisée de SQL, vous pouvez définir votre propre opération MigrationOperation pour la représenter. L’opération est ensuite transmise au fournisseur afin qu’elle puisse déterminer le SQL approprié à être généré.

public class CreateUserOperation : MigrationOperation
{
    public string Name { get; set; }
    public string Password { get; set; }
}

Avec cette approche, la méthode d’extension doit simplement ajouter l’une de ces opérations à 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);
}

Cette approche exige que chaque fournisseur sache comment générer SQL pour cette opération dans son service IMigrationsSqlGenerator. Voici un exemple qui remplace le générateur de SQL Server pour gérer la nouvelle opération.

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

Remplacez le service de générateur SQL des migrations par défaut par celui mis à jour.

protected override void OnConfiguring(DbContextOptionsBuilder options)
    => options
        .UseSqlServer(_connectionString)
        .ReplaceService<IMigrationsSqlGenerator, MyMigrationsSqlGenerator>();