[料理佳餚] Entity Framework Code First 不算太難用

Entity Framework(本篇討論的是 6.x 版本)基本上有 3 種 ORM 的方式:Database First、Model First、Code First。

一般人對 Code First 的恐懼是它必須透過程式碼來控制 Table Schema、Field Attribute…等來完成資料庫的設計,比起用 SSMS 透過 GUI 來操作顯得不是很方便。

Entity Framework 的 Code First 是這樣玩的,舉個簡單例子,有一個 Product 的抽象類別,分別被 Car、CellPhone、Clothing 繼承,各產品子類別各自去表述產品的特性。

建立 DbContext

Entity Framework 可以從 NuGet 上取得,而我們要用 Code First 將這樣子的類別關係表現在資料庫中,首先要建立一個繼承自 DbContext 的類別,我們將它命名為 GoodsContext,一併為其宣告 constructor,在 base contructor 裡面給入連線字串的參數,可以是 <connectionString>  的 name 或是直接是 connectionString 的值都可以。

接著為每個子類別建立自己的資料表,宣告 3 個 DbSet<> 泛型型別的屬性,表達我要分別為 Car、CellPhone、Clothing 建立資料表。

class GoodsContext : DbContext
{
    public DbSet<Car> Cars { get; set; }

    public DbSet<CellPhone> CellPhones { get; set; }

    public DbSet<Clothing> Clothing { get; set; }

    public GoodsContext()
        : base("GoodsContext") // connectionString's name
    {
    }
}

App.Config:

<configuration>
  <connectionStrings>
    <add name="GoodsContext"
         connectionString="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Goods;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>

宣告資料表及欄位的相關屬性

再來,我們要為各資料表宣告資料表名稱、主鍵、欄位大小…等條件,我們可以操作 Fluent API 或是標註 Data Annotations 來達到這個目的。

底下我是使用標註 Data Annotations 的方式來宣告主鍵,原因是 Data Annotations 已經滿足大部分的需求,夠直覺、方便,Data Annotations 辦不到的部分再操作 Fluent API 來做就行了,參考資料有 Fluent API 的相關資訊,有興趣的朋友可以往那邊去。

由於我的每個子類別都是繼承自 Product 這個抽象類別,所以主鍵只要標註在 Product 這個抽象類別的 Id 就可以了,這樣每個子類別對應到的資料表就會自動產生 Id 這個主鍵。

abstract class Product
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public double UnitPrice { get; set; }
}

建立資料表

一切準備就緒後,我們在套件管理器主控台輸入指令 Enable-Migrations

我們會看到專案底下產生了一個 Migrations 資料夾,裡面有一個 Configuration.cs 檔案。

接著輸入指令 Add-Migration Initial

指令執行完之後,我們可以看到在 Migrations 資料夾底下多了一個以時間開頭、Initial 結尾的 cs 檔,這個 cs 檔裡面放的就是實際去執行更新資料庫結構的程式碼。

接下來只要再一個指令,我們可以把資料表給建起來了,輸入 Update-Database 後面加上 -Verbose 參數,加上 -Verbose 的目的是要把詳細訊息顯示出來。

指令執行完成之後,就可以看到資料表被建立好了。

產生更新資料庫結構的指令碼

But,應該沒人有那麼大的膽子直接這樣對資料庫做操作,自己測試用的資料庫那倒還好,如果是線上的資料庫直接這樣搞,我想你應該走不出公司的大門。

所以呢,我們在下 Update-Database 指令時加入 -Script 參數,這樣就不會直接對資料庫做操作,而是產生出更新資料庫結構的指令碼,指令碼產出之後我們把指令碼儲存下來之後,再挑個良辰吉日或是交給 DBA 來手動更新。

總結

Code First 讓我們可以先設計資料類別,然後開始寫 Production Code,之後需要開始塞資料時,再一次把資料庫結構建好,不用再管資料庫那邊要怎麼設定、怎麼調,聽起來好不完美啊!

但是太完美的東西,總是不切實際,真正的情況剛好相反,用 Code First 來寫資料庫程式,我們必須要更嚴謹地來設計資料類別,更清楚地去知道 Entity Framework 對資料庫的影響,來避免我們去踩到效能的紅線。

而 Code First 這種建立資料庫、更新資料庫結構的過程,其實不算太難駕御,整個步驟大致上就是「設計類別」→「產生更新資料庫結構的程式碼」→「直接更新」(或「產生更新資料庫的指令碼」→「手動更新」),真正難的地方是不要讓 Entity Framework 拿我們設計出來的 Model 去建出低效的資料表。

我就曾經看過一個抽象類別,假設叫 AClass,被 7 個子類別繼承,每個子類別各建立一個資料表,偏偏某個類別,假設叫 BClass,底下有一個屬性是 List<AClass>,跟 AClass 建立了一對多的關係,但真正跟 BClass 有關係的其實是那 7 個繼承 AClass 的類別,這應用了物件導向多型的特性,表面上看起來沒什麼問題,於是我就問那位朋友「那這樣要怎麼撈資料?」,得到的答案是「用 Linq 語法或 Labmda 表達式把 7 個資料表 Join 起來,按照條件撈出結果轉型成 List<AClass> 後回傳。」…(請自行想像後來這個系統上線之後的慘狀)

後記

Entity Framework 6.1 針對 Code First 加入了 Code First from database 的功能,稍稍消弭了必須仰賴程式碼來控制資料庫的這份不方便,不過本篇的內容主要是在分享單純的 Code First,至於 Code First from database 這個功能有機會再另闢篇幅來研究研究。

參考資料

 < Source Code >

C# 指南 ASP.NET 教學 ASP.NET MVC 指引
Azure SQL Database 教學 SQL Server 教學 Xamarin.Forms 教學