Пользовательские операции миграции

API MigrationBuilder позволяет выполнять множество различных операций во время миграции, но это далеко не является исчерпывающим. Однако 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}';");

Совет

Используйте функцию EXEC , когда инструкция должна быть первой или единственной в пакете SQL. Кроме того, может потребоваться обойти ошибки синтаксического анализа в сценариях идемпотентной миграции, которые могут возникать, когда ссылки на столбцы в настоящее время не существуют в таблице.

Если миграция должна поддерживать несколько поставщиков баз данных, можно использовать 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);
}

Этот подход требует, чтобы каждый поставщик знал, как создать SQL для этой операции в своей IMigrationsSqlGenerator службе. Ниже приведен пример переопределения генератора SQL Server для обработки новой операции.

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

Замените службу генератора SQL по умолчанию обновленным.

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