自定义迁移操作

MigrationBuilder API 允许在迁移过程中执行多种不同类型的操作,但远不止于此。 但是,该 API 也是可扩展的,允许定义自己的操作。 可通过两种方法扩展 API:使用 Sql() 方法,或定义自定义 MigrationOperation 对象。

为了说明这一点,我们来看一下实现一个使用每种方法创建数据库用户的操作。 在迁移过程中,想要能够编写以下代码:

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

使用 MigrationBuilder.Sql()

实现自定义操作的最简单方法是定义调用 MigrationBuilder.Sql() 的扩展方法。 下面是生成适当 Transact-SQL 的示例。

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

提示

如果某个语句必须是 SQL 批处理中的第一个或唯一一个语句,请使用 EXEC 函数。 可能还需要解决幂等迁移脚本中的分析程序错误,如果表中当前不存在引用的列,则这些错误可能会发生。

如果迁移需要支持多个数据库提供程序,可以使用 MigrationBuilder.ActiveProvider 属性。 以下示例同时支持 Microsoft SQL Server 和 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.");
}

此方法仅在你了解将应用自定义操作的每个提供程序时才有效。

使用 MigrationOperation

要将自定义操作与 SQL 分离,可以定义自己的 MigrationOperation 以表示它。 然后将操作传递给提供程序,以便它可以确定要生成的适当 SQL。

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

使用这种方法,扩展方法只需将这些操作之一添加到 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);
}

该方法要求每个提供程序都知道如何在其 IMigrationsSqlGenerator 服务中为此操作生成 SQL。 下面是一个替代 SQL Server 生成器以处理新操作的示例。

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

将默认迁移 sql 生成器服务替换为更新后的服务。

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