EF Core 多 DbContext 共用資料庫 EnsureCreated 失效問題
0 |
讓 EF Core 依據 Entity 類別定義自動在資料庫新增資料表,主要有兩種做法:使用 Migration 或呼叫 DbContext.Database.EnsureCreated()。
Migration 的好處是可以在 Model 修改時,自動產生 Schema 異動指令(例如:新增、修改欄位),靠dotnet ef database update
一行指令就地升級資料庫結構,好不帥氣。但如之前淺談正式環境資料庫建立與換版所提,正式營運環境想這樣搞無疑痴人說夢。正式台資料庫管制嚴格,豈是想連就連,更甭提上線時所有更動細節需經層層審核,不太可能連要跑什麼指令都不清楚,放任程式自由發揮。
因此,EF Core 的自動建資料表我只用在新系統創建資料庫時,後續 Schema 如需更新,乖乖人工整理指令送審上線。採用這種運作模式,用 EnsureCreated() 手續相對簡單許多。
但 EnsureCreated() 有個問題,它的執行原則是:
- 如果資料庫存在且具有任何資料表,則不會採取任何動作,以確保資料庫架構與 Entity Framework 模型相容。
- 如果資料庫存在但沒有任何資料表,則會使用 Entity Framework 模型來建立資料庫架構。
- 如果資料庫不存在,則會建立資料庫,並使用 Entity Framework 模型來建立資料庫架構。
實務上我很常運到多個 DbContext 共用一個資料庫的狀況,例如:專案有自己的 MyDbContext,將 js、css、jpg 等靜態檔移入 DB 會用到 StaticFileDbContext。
使用相同連線字串指向全新資料庫,建立兩個 DbContext,分別呼叫 Database.EnsureCreated(),只有第一次會建立資料表,第二次因符合「如果資料庫存在且具有任何資料表」不會建立任何資料表:
using Drk.AspNetCore.FileProviders;
using ensurecreated_fail;
using Microsoft.EntityFrameworkCore;
var dbFileName = "shared.sqlite";
var cs = $"data source={dbFileName}";
if (File.Exists(dbFileName)) File.Delete(dbFileName);
using (var statFileDbCtx = new StaticFileDbContext(
new DbContextOptionsBuilder<StaticFileDbContext>()
.UseSqlite(cs).Options))
{
statFileDbCtx.Database.EnsureCreated();
try
{
Console.WriteLine($"Check StaticFileIndices Count = {statFileDbCtx.StaticFileIndices.Count()}");
}
catch (Exception ex)
{
Console.WriteLine("ERROR: " + ex.Message);
}
}
using (var myDbCtx = new MyDbContext(
new DbContextOptionsBuilder<MyDbContext>()
.UseSqlite(cs).Options))
{
myDbCtx.Database.EnsureCreated();
try
{
Console.WriteLine($"Check MyEntities Counte = {myDbCtx.MyEntities.Count()}");
}
catch (Exception ex)
{
Console.WriteLine("ERROR: " + ex.Message);
}
}
試了兩次,第一次先跑 StaticFileDbContext、再跑 MyDbContext,第二次順序交換,都是後執行的因資料表沒建立出錯:
面對這個問題,我採用的簡單解法是第二次開始改用statFileDbCtx.Database.ExecuteSqlRaw(statFileDbCtx.Database.GenerateCreateScript());
取代 EnsureCreated(),如此,兩個 DbContext 的資料表都順利建立:
不過,當資料庫為 SQL,GenerateCreateScript() 輸出結果會包含 GO 指令 (請參考先前文章的擷圖),直接用 ExecuteSqlRaw() 執行會出錯。沒關係,這在使用 C#/PowerShell 執行 SSMS 所產生包含 GO 的 SQL Script 遇過了,嚇不倒我的。
最後修改版本如下:
using (var statFileDbCtx = new StaticFileDbContext(
new DbContextOptionsBuilder<StaticFileDbContext>()
.UseSqlite(cs).Options))
{
var sb = new StringBuilder();
foreach (var line in statFileDbCtx.Database.GenerateCreateScript()
.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
{
if (line == "GO") {
statFileDbCtx.Database.ExecuteSqlRaw(sb.ToString());
sb.Clear();
}
else sb.AppendLine(line);
}
statFileDbCtx.Database.ExecuteSqlRaw(sb.ToString());
}
When two DbContexts share a database, the second EnsureCreated will do nothing. This article shows how to use GenerateCreateScript() to resolve the issue.
Comments
Be the first to post a comment