使用EF Core更新與修改生產資料庫

2022-10-09 06:00:54

使用EF Core的Code First,在設計階段,直接使用Database.EnsureCreated()EnsureDeleted()可以快速刪除、更新最新的資料結構。由於沒有什麼資料,刪除的風險非常低。但是對於已經投入生產的資料庫,這個方法就絕對不可行了。

考慮以下場景:
專案已經上線,一直使用本地測試資料庫進行開發,本地已經增加和修改了較多資料庫表結構,線上資料龐大且實時更新,現在測試完畢需要進行上線。

如果需要更新生產資料庫,我能想的有兩種方法:

從一開始就使用Migration

從資料庫開始設計的時候,就使用EF Migration,保證資料庫能夠與程式碼同步,不過操作的時候,需要極為小心,務必要檢查生成的更新資料庫程式碼,直接連線生產資料庫,

需要注意的事項:

  • 從一開始就使用Migration任何時候都不要使用Context.Database.EnsureCreated或者EnsureDeleted語句。
  • 使用Add-migration之後,不要刪除生成的Migration檔案,這些檔案記錄了資料結構的變化歷史。
  • 並不是所有的變化都能自動識別,比如「修改表列名稱大小寫」,這種情況很多時候生成的資料是執行刪除然後再新建,和我們重新命名的初衷相去甚遠。因此要特別檢查migrationBuilder.Drop相關的頁面。

使用Scaffold

如果一開始就沒有使用migration進行同步的話,那麼使用EF Core將無法直接更新,我們需要變通一下:

逆向資料庫到模型

首先需要資料庫的資料結構逆向到模型,我們使用Scaffold就可以了,詳細檔案就可以檢視這裡,需要注意的是,我們的場景下,已經有修改好的DataContext與Model,在進行scaffold的過程中,一定要指定outputdir和context,不要和當前的檔案衝突。

根據自己的喜好,選擇是否採用-DataAnnotations,另外也可以使用-table指定需要修改的表,沒有被指定的表,將保持原樣。預設EF Core會按照自己的命名規則重新命名,如果你想保留自己的套路,那麼使用-UseDatabaseNames引數。

Add-Migration

輸出的模型我指定放在Models資料夾,原來的Models資料夾,我改成了Models1,並且更換了名稱空間以保證專案現在能夠正常編譯。

  • 匯出的模型與DbConext:Models.Models名稱空間,Models資料夾
  • 新模型與DbConext:Models名稱空間,Models1資料夾
    接下來執行Add-Migration
add-migration initialcreate -context exportedContext

這樣會在Migrations資料夾下面生成一個snapshot和一個migration檔案。snapshot是當前資料庫的跟蹤,另外一個是運用update-database時系統會執行的操作。裡面有一個Up()和一個Down()方法,Up是執行更新時EF對資料庫的操作,Down是回滾當前更改。由於這是第一次執行add-migration,EF Core會認為資料庫現在還是空的,因此兩個方法都有大量的語句,我們刪除所有create和drop相關的語句,我這邊是全部刪除了,只留下空方法。

應用遷移,同步

前面準備工作已經到位了,這一步將直接運算元據庫了。使用update-database將當前的migration更新到資料庫,由於我們現在的資料結構和生產資料庫的資料結構一模一樣,實際上我們不需要執行什麼操作(刪除了Up、Down內部的程式碼),執行Update-Database只是讓EF Core將Models和生產資料庫建立聯絡

我理解只是新增__EFMigrationsHistory中的記錄,以便EF Core後續追蹤。

修改模型內容

將Models1中的檔案覆蓋Models中的檔案,由於型別命名的差異,可能會提示一些錯誤,按照自己的習慣修改就好了。接下來是循序漸進,一點點修改模型,並經常add-migration,觀察生成的語句是否正常。

由於我使用了Identity,在資料中有對應的AspNet開頭的表,這些表我並不在本系統中使用(其他系統需要用),因此我刪除了對應的模型、snapshot、DbContext記錄,執行Add-Migration,生成了如下檔案:

        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "AspNetRoleClaim");

            migrationBuilder.DropTable(
                name: "AspNetUserClaim");

            migrationBuilder.DropTable(
                name: "AspNetUserLogin");

            migrationBuilder.DropTable(
                name: "AspNetUserRoles");

            migrationBuilder.DropTable(
                name: "AspNetUserToken");

            migrationBuilder.DropTable(
                name: "AspNetRole");

            migrationBuilder.DropTable(
                name: "AspNetUser");
        }

說明現在已經能夠正常跟蹤我們的修改了,不過我這裡需要保留對應的表,因此刪除up與down的所有內容。

注意以下幾點:

更新模型名稱

如果使用fluentAPI,那麼模型對應的表名稱會直接在fluentAPI中直接指定,只修改模型的名稱沒有任何效果。修改的話,可以修改對應的fluentAPI,或者換用Annotation

提示找不到constraint

對於修改主鍵、索引等內容的情況,如果不是通過EF Core建立的資料庫,那麼命名規則可能不一樣。對於postgresql資料庫,可以用這個查詢名稱,然後修改對應的migration檔案內容即可。

SELECT * FROM pg_CONSTRAINT

複合主鍵的限制

對於使用兩列或者以上列作為複合主鍵的情況,使用EnsureCreated方法是可以識別Annotation形式的主鍵的。

[Key]
[Column(Order = 1)]
public string DeviceId { get; set; }
[Key]
[Column(Order = 2)]
public long Timestamp { get; set; }

使用Migration的時候,這種形式無法識別,需要在OnModelCreating()中,使用fluentAPI

modelBuilder.Entity<DeviceData>().HasKey(w => new { w.DeviceId, w.Timestamp });

Command執行超時

預設Command執行的超時設定只有30s,對一些大一點的表來說,是不太夠的。可以設定:

optionsBuilder.UseNpgsql("Server=xxxxxxxxxxxxx", opt=>opt.CommandTimeout(3000));

增加命令執行的超時時間。

多個連線字串的情況

如果程式使用了appsettings.Development.json之類的檔案儲存連線字串,那麼需要指定環境是Production(生產資料庫),否則可能還原到本地資料庫去了。
對於nuget包管理控制檯(使用update-database),執行:

$Env:ASPNETCORE_ENVIRONMENT = "Development"
Update-Database

對於使用dotnet ef工具集的,直接執行:

dotnet ef database update --environment Development

cannot be cast automatically to type

設計資料庫表如果修改列的資料型別(比如從varchar到integer),Postgresql會提示這個問題,導致無法修改。可以在migrationbuilder中使用sql,按照提示新增"USING "x"::integer"解決。但是這種方法還是不太優雅,手動處理Up()之後,還需要處理Down(),否則將無法正確還原。

可以使用分步的方法進行,假設我們需要將Id從varchar改成int4

  1. 新增一個欄位temp,型別為int4,設定為[Key],然後刪除Id欄位。
  2. 新增並應用遷移
  3. 修改temp名稱為Id
  4. 新增並應用遷移

多次應用遷移

每次修改儘量少一點,然後update-database,這樣更容易發現問題,對於有

這種提示的,一定要檢查生成語句中Drop相關的語句。

本地資料庫與生產資料庫,都有__EFMigrationsHistory記錄相關的遷移情況。在生產與本地資料庫中進行切換時,不用擔心順序問題,Update-Database會一個個應用遷移直到最新。

總結

使用Migration能夠降低資料庫同步中很多工作量,合理利用,可以對生產用的資料庫進行熱更新。

注:本文在.NET 6,EF Core 6下測試通過。