本文章是由機器翻譯。

資料點

Entity Framework Preview: code first, ObjectSet and DbContext

Julie Lerman

Entity Framework (EF) 4 仍處於測試階段,但開發團隊已開始通過另一種方式使用它了。我們具備用於創建模型的資料庫優先方式(將資料庫反向工程為實體資料模型)和新的 EF 4 模型優先功能(定義實體資料模型,然後從中創建資料庫)。此外,團隊決定創建某種稱為“代碼優先”的功能。

顧名思義,這將從代碼開始 — 既不是資料庫也不是模型。事實上,使用代碼優先功能,則無需可視模型和描述該模型的 XML。您只需為您的應用程式定義域創建類,代碼優先將會使其能夠參與到 EF 中。您可以使用上下文,編寫並執行 LINQ to Entities 查詢,以及利用 EF 更改跟蹤功能。但無需在設計器中處理模型。如果不是要構建高度結構化的應用程式,您可能幾乎已經忘記了一直是 EF 在為您處理資料庫交互和更改跟蹤。

域驅動開發 (DDD) 迷們似乎比較喜歡代碼優先。事實上,正是許多 DDD 行家説明 EF 團隊瞭解了代碼優先對於 DDD 程式師的意義。請查看 Danny Simmons 有關資料可程式設計性諮詢委員會的博客文章,瞭解此方面的更多資訊 (blogs.msdn.com/b/dsimmons/archive/2008/06/03/dp-advisory-council.aspx)。

但是,代碼優先目前還無法包含在 Microsoft .NET Framework 4 和 Visual Studio 2010 中。實際上,其仍處在發展階段,Microsoft 為開發人員提供了對此部分的訪問,以供他們使用並通過 Entity Framework Feature CTP (EF Feature CTP) 提供回饋。此社區技術預覽 (CTP4) 的第四個小版本發佈于 2010 年七月中旬。此小版本中有著重大更改。

除了在 CTP4 中包含代碼優先程式集外,Microsoft 還對下一小版本的 EF 提出了一些很不錯的新創意。這些創意也包括在 CTP4 中,以便使用者可以對其進行體驗並提供回饋。更為重要的是,代碼優先可以利用其中的一些新功能。

您可以通過 EF 團隊 (tinyurl.com/297xm3j)、Scott Guthrie (tinyurl.com/29r3qkb) 和 Scott Hanselman (tinyurl.com/253dl2g) 的博客文章瞭解有關此 CTP 的一些詳細演練資訊。

在本專欄中,我將重點介紹 CTP4 中的一些獨特功能和改進,這些功能和改進大幅簡化了使用 EF(通常和 .NET Framework 一起)進行的開發,從而引起了一場有關 EF 的極大轟動。

核心 API 的改進

在 CTP4 添加到 EF 運行時的改進中,值得一提的是兩個主力類(ObjectContext 和 ObjectSet)的輕型版本。ObjectContext 支援查詢、更改跟蹤和保存回資料庫。ObjectSet 用於封裝類似物件集。

新類 DbContext 和 DbSet 具有相同的基本功能,但不會讓您接觸到 ObjectContext 和 ObjectSet 的整個功能集。並不是每一個編碼方案都需要訪問更強大類中的所有功能。DbContext 和 DbSet 並非派生自 ObjectContext 和 ObjectSet,但 DbContext 通過一個屬性簡化了對功能完備版本的訪問:DbContext.ObjectContext。

ObjectSet 基礎知識與簡化的 DbSet

ObjectSet 是一組實體類型的類似泛型集合的表示。例如,您可以將一個 ObjectSet<Customer>稱為 Customers,這是上下文類的一個屬性。LINQ to Entities 查詢是依據 ObjectSet 編寫的。以下是一個依據 Customers ObjectSet 編寫的查詢:

from customer in context.Customers 
where customer.LastName=="Lerman" 
select customer

ObjectSet 具有內部構造函數,但沒有無參數構造函數。 ObjectSet 是通過 ObjectContext.CreateObjectSet 方法創建的。 在典型的上下文類中,Customers 屬性將定義為:

public ObjectSet< Customer> Customers {
  get { 
    return _ customers??
(_customers = 
      CreateObjectSet< Customer >(" Customers"));
  }
}

private ObjectSet< Customer > _ customers;

現在,您可以依據 Customers 編寫查詢,並操作該集,根據需要添加、附加和刪除物件。

DbSet 的使用更加簡單。 DbSet 不可公開構造(類似于 ObjectSet),但它將自動創建 DbSet 並將其分配至您在派生的 DbContext 上聲明的任何屬性(具有公共 setter)。

ObjectSet 集合方法中引入了 EF 術語 — AddObject、Attach 和 DeleteObject:

context.Customers.AddObject(myCust)

DbSet 只使用 Add、Attach 和 Remove,與其他集合方法名稱更為一致,因此,您不必花費時間來解決“如何在 EF 中表述”這樣的問題,如:

context.Customers.Add(MyCust)

使用 Remove 方法而非 DeleteObject 也更能清晰地描述該方法正在執行的操作。該方法將從集合中刪除某項。DeleteObject 建議從資料庫中刪除資料,這會給很多開發人員造成混淆。

迄今為止,DbSet 讓我喜歡的原因之一是派生類型的使用更為簡單。ObjectSet 包括基本類型和從其繼承的所有類型。如果您具有基本實體 Contact 和從其派生的 Customer 實體,則每次要處理客戶事宜時,都必須從 Contacts ObjectSet 開始。例如,要查詢客戶,您需要編寫 context.Contacts.OfType<Customer>。這不僅會造成混亂,而且還不清晰。DbSet 可以包含派生類型,因此,您現在可以創建一個屬性 Customers,以返回 DbSet<Customer>並支援與之直接交互而不是通過 Contacts 集。

DbContext — 簡化的 Context

DbContext 公開了 ObjectContext 最常用的功能,並提供了一些確實有用的附加功能。到目前為止,我所喜歡的 DbContext 的兩個功能是泛型 Set 屬性和 OnModelCreating 方法。

我目前所指的所有那些 ObjectSet 都是 ObjectContext 實例的顯式屬性。比方說您有一個稱為 PatientHistory 的模型,其中含有三個實體:Patient、Address 和 OfficeVisit。您將有一個繼承 ObjectContext 的類 PatientHistoryEntities。此類包含一個 Patients 屬性(即 ObjectSet<Patient>)、一個 Addresses 屬性以及一個 OfficeVisits 屬性。如果希望使用泛型編寫動態代碼,您必須調用 context.CreateObjectSet<T>,其中 T 是您的實體類型之一。這依舊不是很清晰。

DbContext 的 Set 方法更加簡單,通過該方法,您只需調用將會返回 DbSet<T>的 context.Set<T>即可。看起來好像只是少了 12 個字母,但對於我的編碼大腦來說,使用此屬性感覺棒極了,而調用工廠方法則不儘然。您還可以將派生實體與此屬性配合使用。

另一個 DbContext 成員是 OnModelCreating,它在代碼優先中很有用。您希望應用到域模型的任何附加配置都可在 EF 基於域類構建記憶體中中繼資料之前,在 OnModelCreating 中進行定義。這是以前版本的代碼優先從未有過的重大改進。您將在後文中看到關於此改進的更多資訊。

代碼優先更智慧、更方便

2009 年 6 月,代碼優先首先作為 EF Feature CTP1 的一部分呈現給開發人員,命名為“僅代碼”。這種使用 EF 的變體背後的基本前提是開發人員只希望定義其域類,而不用為物理模型費心。然而,EF 運行時依賴該模型的 XML 來根據模型將查詢強制為數據庫查詢,然後將查詢結果從資料庫強制回由模型描述的物件。如果沒有中繼資料,則 EF 無法完成此作業。但中繼資料不必位於物理檔中。EF 在應用程式進程期間讀取這些 XML 檔一次,基於該 XML 創建強類型化中繼資料物件,然後完成所有與記憶體中 XML 之間的交互。

代碼優先也將創建記憶體中中繼資料物件。但是,不是通過讀取 XML 檔來創建,而是從域類推斷中繼資料(請參閱圖 1)。它使用約定實現此目的,然後提供可供您添加附加配置以進一步細化模型的方法。

圖 1 代碼優先在運行時構建實體資料模型中繼資料

代碼優先的另一個重要作業是使用中繼資料創建資料庫架構,甚至創建資料庫本身。代碼優先自其最早的公共版本發佈以來就一直提供這些功能。

在以下示例中,您需要用一個配置來替代一些無效的假設。我有一個稱為 ConferenceTrack 的類,它具有一個稱為 TrackId 的識別屬性。代碼優先約定將查找“Id”或類名 +“Id”(作為要用於實體的 EntityKey 的識別屬性和資料庫表的主鍵)。但 TrackId 不適合此模式,因此我必須告訴 EF 這是我的標識鍵。

新的代碼優先 ModelBuilder 類基於之前描述的類構建記憶體中模型。您可以使用 ModelBuilder 進一步定義配置。我能夠通過以下 ModelBuilder 配置指定 ConferenceTrack 實體應使用其 TrackId 屬性作為其鍵:

modelBuilder.Entity<ConferenceTrack>().HasKey(
  ct => ct.TrackId);

現在,ModelBuilder 將在創建記憶體中模型並構建資料庫架構時,將此附加資訊考慮在內。

配置應用更具邏輯性

在這方面,DbContext.OnModelCreating 非常有用。 您可以將配置置於此方法中,以便在 DbContext 創建模型時應用這些配置:

protected override void OnModelCreating(
  ModelBuilder modelBuilder) { 
  modelBuilder.Entity<ConferenceTrack>().HasKey(
    ct => ct.TrackId); 
}

CTP4 中新增的另一個功能是另一種通過類中的屬性應用配置的方法。 這項技術稱為資料批註。 通過將 Key 批註直接應用到 ConferenceTrack 類中的 TrackId 屬性,可以得到相同的配置:

[Key] 
public int TrackId { get; set; }

確實更加簡單,但是,我個人喜好使用程式設計配置,這樣,類不必在其中具有任何 EF 特定的內容。

使用此方法還意味著 DbContext 將負責緩存模型,因此進一步構造 DbContext 不會再次帶來模型發現成本。

關係更加簡單

代碼優先中最值得一提的改進之一是,通過類進行假設更加智慧。 對這些約定的改進有很多,但我發現,關係約定的增強對我的代碼影響最大。

儘管關係是在類中進行定義的,但在之前的 CTP 中,必須提供配置資訊以在模型中定義這些關係。 此外,這些配置既亂又無邏輯性。 現在,代碼優先可以正確解釋類中定義的多數關係的用途。 在需要通過一些配置調整模型時,語法更加簡單。

我的域類通過屬性定義了很多關係。 例如,ConferenceTrack 具有此種一對多的關係:

public ICollection<Session> Sessions { get; set; }

Session 具有相反的關係以及多對多關係:

public ConferenceTrack ConferenceTrack { get; set; }
public ICollection<Speaker> Speakers { get; set; }

使用我的類和單一配置(將 TrackId 定義為會議的金鑰),模型生成器創建了具有所有關系和繼承的資料庫,如圖 2 所示。

圖 2 建模創建的資料庫結構

請注意,Sessions_Speakers 表是針對多對多關係創建的。我的域具有一個從 Session 繼承的 Workshop 類。預設情況下,代碼優先假定“每個層次結構一張表”繼承,因此它在 Sessions 表中創建了 Discriminator 列。我可以使用配置將其名稱更改為 IsWorkshop,甚至指定它應創建一個“每個類型一張表”繼承。

這些新功能的事先計畫

這些可以在 CTP4 中搶先使用的引人注目的新功能可能會使那些表示“我現在就想要!”的開發人員倍感沮喪。這僅僅是個預覽版,目前還無法運用到生產實踐中,當然,如果能夠向 EF 團隊提供回饋以説明其進行完善,您將有機會試用這些新功能。您可以事先計畫如何在您即將推出的應用程式中使用代碼優先以及其他新的 EF 功能。

Julie Lerman 是 Microsoft MVP、.NET 導師和顧問,住在佛蒙特州的山區。您可以在全球的使用者組和會議中看到她對資料訪問和其他 Microsoft .NET 主題的演示。Lerman 是《Programming Entity Framework》(O'Reilly Media,2009)一書的作者,該書受到廣泛稱讚,她的博客位址是 thedatafarm.com/blog。Follow her on Twitter.com: julielerman.

衷心感謝以下技術專家對本文的審閱: Rowan Miller