使用EF Core更新與修改生產數據庫

使用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下測試通過。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章