自訂遷移作業Custom Migrations Operations

MigrationBuilder API 可讓您在遷移期間執行許多不同種類的作業,但其實遠不多。The MigrationBuilder API allows you to perform many different kinds of operations during a migration, but it's far from exhaustive. 不過,此 API 也是可擴充的,可讓您定義自己的作業。However, the API is also extensible allowing you to define your own operations. 有兩種方式可以擴充 API:使用 Sql() 方法,或定義自訂 MigrationOperation 物件。There are two ways to extend the API: Using the Sql() method, or by defining custom MigrationOperation objects.

為了說明,讓我們看看如何使用每個方法來建立資料庫使用者。To illustrate, let's look at implementing an operation that creates a database user using each approach. 在我們的遷移中,我們想要啟用撰寫下列程式碼:In our migrations, we want to enable writing the following code:

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

使用 MigrationBuilder ( # A1Using MigrationBuilder.Sql()

執行自訂作業最簡單的方式,就是定義呼叫的擴充方法 MigrationBuilder.Sql()The easiest way to implement a custom operation is to define an extension method that calls MigrationBuilder.Sql(). 以下是產生適當 Transact-sql 的範例。Here is an example that generates the appropriate Transact-SQL.

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

如果您的遷移需要支援多個資料庫提供者,您可以使用 MigrationBuilder.ActiveProvider 屬性。If your migrations need to support multiple database providers, you can use the MigrationBuilder.ActiveProvider property. 以下是支援 Microsoft SQL Server 和于 postgresql 的範例。Here's an example supporting both Microsoft SQL Server and PostgreSQL.

static MigrationBuilder 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}';");
    }

    return migrationBuilder;
}

只有當您知道將套用自訂作業的每個提供者時,此方法才會運作。This approach only works if you know every provider where your custom operation will be applied.

使用 MigrationOperationUsing a MigrationOperation

若要將自訂作業與 SQL 分離,您可以定義自己的操作 MigrationOperation 來代表它。To decouple the custom operation from the SQL, you can define your own MigrationOperation to represent it. 作業接著會傳遞給提供者,以便判斷要產生的適當 SQL。The operation is then passed to the provider so it can determine the appropriate SQL to generate.

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

使用這個方法時,擴充方法只需要將這些作業的其中一項加入至 MigrationBuilder.OperationsWith this approach, the extension method just needs to add one of these operations to MigrationBuilder.Operations.

static MigrationBuilder CreateUser(
    this MigrationBuilder migrationBuilder,
    string name,
    string password)
{
    migrationBuilder.Operations.Add(
        new CreateUserOperation
        {
            Name = name,
            Password = password
        });

    return migrationBuilder;
}

這種方法需要每個提供者知道如何在其服務中為這項作業產生 SQL IMigrationsSqlGeneratorThis approach requires each provider to know how to generate SQL for this operation in their IMigrationsSqlGenerator service. 以下範例會覆寫 SQL Server 的產生器,以處理新的作業。Here is an example overriding the SQL Server's generator to handle the new operation.

class MyMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator
{
    public MyMigrationsSqlGenerator(
        MigrationsSqlGeneratorDependencies dependencies,
        IMigrationsAnnotationProvider 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 產生器服務。Replace the default migrations sql generator service with the updated one.

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