The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何在正式環境套用 Entity Framework Code First 資料庫移轉(Migrations)

要套用 Entity Framework Code First Migrations 資料庫移轉,一共有 5 種方法,每種方法都有著不同的適用情境,有些方法適用於開發測試環境,有些方法則適用於正式部署的環境。今天這篇文章我就來介紹幾種不同的套用資料庫移轉方法。

  1. 產生 T-SQL 指令碼(適用於部署正式環境)

    從無到有建立全新資料庫的 DDL 指令碼

    dotnet ef migrations script
    

    從特定 MigrationId 產生變更指令碼

    dotnet ef migrations script MigrationId
    

    從特定 MigrationId 到特定 MigrationId 之間的變更指令碼

    dotnet ef migrations script MigrationId1 MigrationId2
    

    他的每一次 Migration 產生的 T-SQL 大概長這樣,每個 Migration 都會套用一次交易:

    BEGIN TRANSACTION;
    GO
    
    DECLARE @var3 sysname;
    SELECT @var3 = [d].[name]
    FROM [sys].[default_constraints] [d]
    INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
    WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Course]') AND [c].[name] = N'Title');
    IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [Course] DROP CONSTRAINT [' + @var3 + '];');
    ALTER TABLE [Course] ALTER COLUMN [Title] nvarchar(55) NULL;
    GO
    
    INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
    VALUES (N'20230822130004_AddTo55', N'7.0.10');
    GO
    
    COMMIT;
    GO
    

    這種方法會需要你自行瞭解確切的 Migration 版本差異,並產生相對應的 T-SQL 腳本,使用上較不方便,但產生出來的變更指令碼會比較小,而且產生出來的 T-SQL 可以人工檢視與驗證,避免正式環境上的資料庫在更新的過程受到負面的衝擊。

  2. 產生冪等的 T-SQL 指令碼(適用於部署正式環境)

    所謂的「冪等的 T-SQL 指令碼」(Idempotent SQL Scripts) 就是可以讓你不用擔心正式環境的資料庫目前到底套用了哪些 Migration 版本,他會產生所有的 Migration 歷史紀錄,並且自動找到正式環境上的關聯式資料庫中沒有的 Migration 版本,並且套用變更指令碼上去。

    dotnet ef migrations script --idempotent
    

    他的每一次 Migration 產生的 T-SQL 大概長這樣,每個 Migration 都會套用一次交易:

    BEGIN TRANSACTION;
    GO
    
    IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20230822130004_AddTo55')
    BEGIN
        DECLARE @var3 sysname;
        SELECT @var3 = [d].[name]
        FROM [sys].[default_constraints] [d]
        INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
        WHERE ([d].[parent_object_id] = OBJECT_ID(N'[Course]') AND [c].[name] = N'Title');
        IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [Course] DROP CONSTRAINT [' + @var3 + '];');
        ALTER TABLE [Course] ALTER COLUMN [Title] nvarchar(55) NULL;
    END;
    GO
    
    IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20230822130004_AddTo55')
    BEGIN
        INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
        VALUES (N'20230822130004_AddTo55', N'7.0.10');
    END;
    GO
    
    COMMIT;
    GO
    

    這種方法你就不需要自行瞭解確切的 Migration 版本差異,但他會產生所有的變更指令碼,缺點就是產生出來的變更指令碼會比較大,對 DBA 來說就比較沒那麼友善,但是對開發人員來說是完美的。

  3. 直接透過 EF Core CLI 工具更新資料庫(適用於開發測試環境)

    套用所有的 Migration 移轉記錄

    dotnet ef database update
    

    從第一版套用到指定的 Migration 版本

    dotnet ef database update MigrationId
    

    開發測試環境比較能讓你直接透過 EF Core 的 CLI 工具更新資料庫,正式環境就比較不可能這樣玩。

  4. 透過 Migration bundles 產生專門用來執行資料庫移轉的執行檔(適用於 CI/CD 環境)

    你可以透過以下指令建立一個包含所有資料庫移轉記錄的「單一可執行檔」,而且是支援跨平台的執行檔,幫助你可以透過該執行檔在目標電腦執行程式,連接目標電腦上的資料庫套用資料庫移轉!

    例如我們要產生一個可以在 Windows 上面執行的檔案,就可以這樣產生:

    dotnet ef migrations bundle --self-contained -r win10-x64
    

    若要產生一個可以在 Linux 上面執行的檔案,就可以這樣產生:

    dotnet ef migrations bundle --self-contained -r linux-x64
    

    如果不加上 --self-contained 參數,則輸出的執行檔較小,但目標電腦需事先安裝 .NET Runtime 才能跑。

    上述命令執行完之後,會產生一個 efbundle 的可執行檔,這個執行檔會包含所有移轉的紀錄,讓你只要擁有這個單一執行檔就可以執行資料庫移轉作業,因此在套用資料庫更新方面非常的方便好用。其執行的方式如下:

    efbundle.exe --connection "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=ContosoUniversity;Integrated Security=True"
    

    efbundle.exe 執行檔所在目錄有包含 appsettings.json 的話,或是 DbContext 資料內容類別中的 OnConfiguring() 方法有定義連接字串,那麼 efbundle 執行檔就會自動取得連接字串,不用特別再加上 --connection 參數。輸入 efbundle.exe --help 可查詢完整參數用法。

    efbundle.exe --help

  5. 在應用程式啟動時自動執行資料庫移轉(適用於開發測試環境)

    若為 ASP.NET Core 應用程式,你可以在 Program.csvar app = builder.Build(); 之後,執行以下程式碼,就會在每次啟動網站時,自動檢查是否有需要套用資料庫移轉的資料庫更新!

    using (var serviceScope = app.Services.CreateScope())
    {
        var services = serviceScope.ServiceProvider;
        var context = services.GetRequiredService<ContosoUniversityContext>();
        context.Database.Migrate();
    }
    

    請注意:千萬不要在 context.Database.Migrate(); 之前執行 context.Database.EnsureCreated() 方法,否則執行 Migrate() 方法時會掛掉!

    由於 Migrate() 方法是基於 IMigrator 服務的,你可以透過以下程式碼取得 IMigrator 的物件實例,並可進階的呼叫 GenerateScript(String, String, MigrationsSqlGenerationOptions), Migrate(String)MigrateAsync(String, CancellationToken) 方法:

    var migrator = context.GetInfrastructure().GetService<IMigrator>();
    

相關連結

留言評論