使用EF Core的Code First,在設計階段,直接使用Database.EnsureCreated()和EnsureDeleted()可以快速刪除、更新最新的數據結構。由於沒有什麼數據,刪除的風險非常低。但是對於已經投入生產的資料庫,這個方法就絕對不可行了。 考慮以下場景: 項目已經上線,一 ...
使用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
。
- 添加一個欄位temp,類型為int4,設置為[Key],然後刪除Id欄位。
- 添加並應用遷移
- 修改temp名稱為Id
- 添加並應用遷移
多次應用遷移
每次修改儘量少一點,然後update-database
,這樣更容易發現問題,對於有
這種提示的,一定要檢查生成語句中Drop相關的語句。
本地資料庫與生產資料庫,都有__EFMigrationsHistory記錄相關的遷移情況。在生產與本地資料庫中進行切換時,不用擔心順序問題,Update-Database會一個個應用遷移直到最新。
總結
使用Migration能夠降低資料庫同步中很多工作量,合理利用,可以對生產用的資料庫進行熱更新。
除非特殊說明,本作品由podolski創作,採用知識共用署名 4.0 國際許可協議進行許可。歡迎轉載,轉載請保留原文鏈接~喜歡的觀眾老爺們可以點下關註或者推薦~註:本文在.NET 6,EF Core 6下測試通過。