學習 ASP.NET Core 的過程發現 Entity Framework Core 已成基本技能,應用廣泛,像 ASP.NET Core Identity 就少不了它,不懂點觀念很難在江湖走跳。

這系列筆記是這陣子的爬文心得整理,目的在涵蓋基本觀念與術語,細節部分則需參考相關文件或教學。

【參考資料】

  1. Entity Framework 源自 2008,隨 .NET 3.5 SP1 一起推出,4.1 起改以 NuGet 程式庫發行,最新版為 EF6,只支援 Windows .NET 4.x Framework。

  2. .NET Core 是未來趨勢,EF Core 是 Entity Framework 的進化版,介面相似但底層幾近砍掉重練,以輕巧、可擴充及跨平台取勝,比 EF6 更容易使用,效能更佳。

  3. EF Core 比 EF6 多了一些功能,但有些 EF6 功能 EF Core 還沒實作或不再支援。(比較表)

  4. EF 以 Model 為中心,定義好 Model,更換 Data Provider 即可改用不同資料庫,EF Core 內建 SQL Server、SQLite、InMemory(僅測試用,無法持久保存資料) 三種資料庫實作。

  5. 應用 SOP:先定義好 Model,繼承 Microsoft.EntityFrameworkCore.DbContext 宣告自訂 DbContext (Repository Pattern),DbContext 透過 DbSet<TModel> 屬性涵蓋涉及的 Model 型別對映應資料表對象。 例如某資料庫有 Blog 及 Post 兩個資料表,其 DbContext 長得像這樣:

     public class BloggingContext : DbContext
     {
         public DbSet<Blog> Blogs { get; set; }
         public DbSet<Post> Posts { get; set; }
    
         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
         {
             optionsBuilder.UseSqlServer(
                 @"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True");
         }
     }
    
  6. EF Core 目前最新版為 2.2 版,EF Core 3.0 預計 9 月會正式發佈。

  7. EF 時代允許從現有資料庫取得 Schema 產生 EDMX 檔並將資料表轉成 Entity 型別。EF Core 時代以 Code First 為主流(先定義 Entity 型別,再產生建立資料表的 SQL Script),但仍保留透過逆向工程產生 DbContext 及 Model 的做法。

  8. EF Core 的安裝方法與入門使用,官方文件有粉詳細的介紹可參考,不用費心找教學。

  9. 連線字串:.NET Framework 可寫在 web.config 或 app.config,用 ConfigurationManager 取得;ASP.NET Core 則可寫在 appsettings.json, 在 Startup.cs 以 Configuration.GetConnectionString() 讀取

  10. Logging:使用 AddDbContext() 或 AddDbContextPool() 加入 EF Core DbConext 後,執行過程的偵錯輸出會自動整合到 ASP.NET Core 的 Logging 機制, 甚至能觀察到 SQL 指令:細節

  11. 連線恢復能力(Connection Resiliency),EF Core 支援資料庫出錯自動重試。

  12. 測試支援:有 SQLite In-Memory mode 及 InMemory Provider 兩種方式,以記憶體模擬關聯資料庫,省去不必要 IO。

  13. DbContext 是 EF Core 的必要型別,設計時期工具以其為依據產生建立及升級資料表的 SQL Script。每個 DbContext 需要一個 DbContextOptions 執行個體,其包含以下資訊:

    • 使用哪個資料庫 Provider?做法是呼叫 .UseSqlServer()、.UseSqlite() 等套件提供擴充方法。
    • 連線字串
    • 各 Provider 專屬的行為設定方法(一般寫在 UseXXXX() 方法中,以串接方式呼叫,如以下程式範例的 CommandTimeout)
    • EF Core 通用的行為設定方法(以串接方式呼叫,如以下程式範例的 UseQueryTrackingBehavior )
    optionsBuilder
        .UseSqlServer(connectionString, providerOptions=>providerOptions.CommandTimeout(60))
        .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
    

    另外也可選擇 DbContext 覆寫 OnConfiguration 方法,在其中進行設定。

  14. 在 ASP.NET Core 主要會以 DI 方式使用 DbConext,如:(延伸閱讀:不可不知的 ASP.NET Core 依賴注入)

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<BloggingContext>(options => options.UseSqlite("Data Source=blog.db"));
    }
    

    DbContext 建構式則要寫成

    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }
    
  15. 注意:EF Core DbContext Instance 不支援平行存取(async 或多執行緒呼叫),永遠要加上 await,平行處理時各用自己的 Instance。 如果你試圖多條執行緒同時呼叫或是忘了 await 又發動另一個 async 呼叫,將會觸發 InvalidOperationException。
    14. 點提到的 AddDbContext() 會註冊成 Scoped 生命週期(每個 Request 過程共用同一個 Instance)

  16. Model 的學問很多,另開一篇筆記專文整理。

  17. 資料庫 Schema 維護
    當 Model 與資料庫不一致時,有兩種選擇:以 Model 為準,透過 Migration 機制去更新資料庫;以資料庫現況為準,透過逆向工程更新 Model Entity。

  18. Migration
    當 Model 與資料庫不一致,砍掉資料表重建最省事,但實務上惡搞正式資料會引來殺身之禍。EF Core 有個聰明的機制,可比對現有資料庫與 Model 的差異產生修改 Schema 用的 SQL Script,在不損失資料的前題下將資料庫改成你要的形狀,這個機制稱之為 Migration。

  19. Migration 工具 可使用 PowerShell 或 dotnet 命令列工具執行。
    Add-Migration InitialCreate(PS) 或 dotnet ef migrations add InitialCreate 會建立 MyContextModelSnapshot.cs (目前 Model 的快照) 及 XXXXXXXXXXXXXX_InitialCreate.cs (定義升級或降級動作,XXX...為時間標籤)、XXXXXXXXXXXXX_InitialCreate.Designer.cs (Metadata)
    Update-Databasedotnet ef database update 將前述建立的升級動作套用到資料庫
    Script-Migrationdotnet ef migrations script 產生升級動作對應的 SQL Script,供後續手動執行。(正式環境應該會採取這種做法)
    另外,呼叫 myDbContext.Database.Migrate(); 可透過程式執行資料庫升級。(應該就是小試 ASP.NET Core Identity裡按下「Apply Migrations」進行的動作) 注意:不是所有 Model 修改都能靠 Migration 自動搞定,各 Provider 的支援程度也不盡相同,例如 SQLite Provider 能支援的修改就比 SQL 少,無法 AddPrimaryKey、AlterColumn、DropColumn... 等等。

  20. 測試環境的 Migration 問題:在測試環境可能因開發者 Model 版本與資料庫差異導致 MyContextModelSnapshot.cs 不同,要留意版控的合併與衝突排除。

  21. MigrationBuilder API 允許客製化及調整 Migration 程序。實務上有時也會將 MyContextModelSnapshot 等類別放在獨立檔案, 區分測試環境及正式環境方便管理。

  22. EF Core 資料庫會有一個 __EFMigrationsHistory 資料表記錄升級歷程,必要時可以客製調整。

  23. Create 與 Drop API 是 Migration 之外的輕量化選擇,適用於資料不重要,每次升級刪除重建即可的場合。

  24. EF Core 2.x 可用於 .NET Core 2.x、.NET Framework 4.6.1、Mono 5.4、Xamarin.iOS 10.14、Xamarin.Mac 3.8、Xamarin.Android 7.5、.NET Core UWP 6.x

  25. Provider 部分,EF Core Project 官方提供 SQL、SQLite、InMemory、Azure Cosmos 四種。 但目前已有第三方維護的 PostgreSQL、MySQL、MarialDB、MyCAT、SQL Server Compact 4.0/3.5、Firebird 2.5/3.x、OracleDB 11.2、Db2、Infomix、MS Access、Progress OpenEdge... 等 Provider 可用,市場已算成熟。因為工作關係,我格外關注 Oracle,NuGet 上有 Oracle.EntityFrameworkCore, 由 Oracle .NET Team 負責維護,可認定已獲官方支援。

Brief introduction of EF Core


Comments

# by Erik Lam

暗黑大, 本人實用需要用到大量stored procedure, 但EF core 3.1 起好像不太支援SP 的custom Return type. 爬文找了半天都找不到, 所以想問一問如果要用到SP 應該怎樣做。

# by 喬治哥

Erik Lam 不太確認是否符合你的需求,我是使用 FromSqlRaw 搭配 OracleParameter 呼叫 Oracle Function,或許可以 try try 看

# by Sean

請教一下黑暗大 有一個需求我直接使用FromSqlInterpolated,取回一個複雜的table Join 的 多個欄位回來 也自行建立一個Class來承接 Select回來的資料 但執行期確會出現 InvalidOperationException: Cannot create a DbSet for 'UserAuth' because this type is not included in the model for the context. 實務上,像這種實際上沒有對應DB Table的資料,但又想結合EF Core進行 ORM (CRUD 只需要 R )一般是都怎麼處理的呢? 感謝

# by Jeffrey

to Sean,我習慣用 Dapper 簡單搞定,不在這上面糾結 https://blog.darkthread.net/blog/dapper-with-ef-core/ 但好消息是 EF Core 8 支援無對映的 Model (但我應該是會用 Dapper,已經順手了) https://github.com/dotnet/efcore/issues/10753

# by Sean

感謝黑暗大的回應 之前也都是搭配Dapper 自建 Repository 做 ORM 回到原發問問題 後來有找到方式,但不知道是不是正規作法 於DbContext裡 public virtual DbSet<ModelName> ModelName{ get; set; } OnModelCreating裡面 modelBuilder.Entity<ModelName>(entity => { entity.HasNoKey(); entity.ToTable(nameof(ModelName), t => t.ExcludeFromMigrations()); }); 如此作法可以讓 FromSqlInterpolated 至自訂的ViewModel 只是不知道此作法好不好

Post a comment