3

EF Core 多 DbContext 共用資料庫 EnsureCreated 失效問題

 1 year ago
source link: https://blog.darkthread.net/blog/efore-share-db-ensurecreated-issue/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

EF Core 多 DbContext 共用資料庫 EnsureCreated 失效問題-黑暗執行緒

讓 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,第二次順序交換,都是後執行的因資料表沒建立出錯:

Fig1_638061053281907353.png

面對這個問題,我採用的簡單解法是第二次開始改用statFileDbCtx.Database.ExecuteSqlRaw(statFileDbCtx.Database.GenerateCreateScript());取代 EnsureCreated(),如此,兩個 DbContext 的資料表都順利建立:

Fig2_638061053285366581.png

不過,當資料庫為 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());
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK