본문 바로가기
Entity Framework Core

Entity Framework Core] Custom Migrations Operations

by Fastlane 2023. 7. 20.
728x90
반응형

MigrationBuilder API를 사용하여, custom operations을 정의할 수 있다. API를 확장하기 위한 2가지 방법이 있다. 

Sql() 함수를 사용하거나, custom MigrationOperation objects를 정의할 수 있다. 

 

migrations에서 다음의 code를 사용해서 database user를 생성하는 operation을 각각의 방법으로 구현해보자.

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

Using MigrationBuilder.Sql()

custom operation 구현의 가장 쉬운 방법은 MigrationBuilder.Sql()를 호출하는 extension method를 정의하는 것이다. 

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

여러개의 db providers를 지원해야 한다면, MigrationBuilder.ActiveProvider property를 사용할 수 있다. 다음과 같이, MS SQL과 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.");
}

custom operation이 적용되어야 하는 모든 provider를 알고 있을 때 유효하다. 

Using a MigrationOperation

custom operation을 SQL과 분리시키기 위해서, MigrationOperation을 직접 정의할 수 있다. 

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

With this approach, the extension method just needs to add one of these operations to MigrationBuilder.Operations.

이 방법으로, 확장 함수는 단지 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);
}

이 방법은, 각 provider가 이 operation을 위해 어떻게 SQL을 생성해야 하는지 알아야 한다. 새로운 operation을 다루기 위해, SQL Server's generator를 overrideing한다. 

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

기본 migration sql generator service를 업데이트 된 것으로 대체한다. 

protected override void OnConfiguring(DbContextOptionsBuilder options)
    => options
        .UseSqlServer(_connectionString)
        .ReplaceService<IMigrationsSqlGenerator, MyMigrationsSqlGenerator>();
728x90
반응형

댓글