第一章:歡迎來到Code First
(譯者注:為方便起見,不再直譯Code First)
在編寫代碼前構建EF模型
在使用.Net 3.5和Visual Studio 2008構建的第一代EF中,給予了開發者通過將現存數據庫轉化為XML文件的形式來創建概念模型的能力。這種XML文件使用EDMX擴展名,可以使用設計器來查看和定制模型以便更好地適應程序域。Visual Studio 2010 and .NET 4帶來了EF的新版本:稱之為Entity Framework 4 (EF4)。EF4還引入了一種稱為Model First的功能,通過此功能可以先創建概念模型然后根據此模型再創建數據庫。
Model First 允許開發者在沒有傳統的數據庫時就可以開始一個新的項目,這是EF框架帶來的好處。開發者可以通過設計概念模型而首先關注應用程序域,從而讓數據庫的創建工作水到渠成。
無論是通過database-first還是通過model-first設計EDMX,下一步都是為了創建您的域以便讓自動代碼生成器基於實體以及找到的各種關系來創建類。通過這種方式,開發人員擁有強類型的類來指向域對象,無論它們是客戶,棒球卡,還是童話人物,都可以圍繞着這些類來愉快地開發軟件應用。
另一個關鍵的變化在EF4。在.NET 3 5,實體框架只能夠管理處於內存中的對象。這些對象的類都繼承自實體框架的EntityObject對象。EntityObject對象向實體框架通知變更,並由實體框架跟蹤這些變化並最終持久化到數據庫中。NET 4中不僅仍有這些功能,還推出了 POCO(Plain Old CLR Object,簡單傳統CLR對象),支持實體框架跟蹤簡單類的變化而不需要EntityObject的參與,這使開發者可以自由使用自己的類,從而獨立於實體框架。EF在運行時可以通過監視對象在內存中的行為保持對這些類的響應和跟蹤。
Code First的發起
基於前面介紹的有關EF4的片斷,我們可以看到,微軟已經提供了多途徑的建模方式,其中有一種新的方式是在EF發布之初,開發人員就所期待的。這種新型的建模方法就是所謂的Code First. Code first 首先讓您定義域模型,而不是使用基於XML的edmx文件。即使采用Model First或Database First來生成代碼,開發者也必需使用設計器或類生成器來輔助工作。而使用Code First您可以直接通過使用POCO類來定義域模型,而無需依賴於EF框架。Code First可以通過構建的類模型推斷出大量信息。你也可以提供額外的配置,進一步描述有關模型的信息或者是覆寫Code Firstr推斷的信息。這種配置也是用代碼來定義的,不需要XML文件或設計器。
使用設計器工作的EF4也支持POCO技術。EF開發團隊提供了一種POCO模板來生成POCO類(可以使用NuGet下載這種T4模板--譯者注)。這些生成的類可以隨着設計器的更改而自動更新。也可以使用你自己創建的POCO類。但如果你決定這樣做,就需要負責保持您的類與EDMX文件的同步。這意味着任何變更都必須在兩個位置進行。Code First的一個最大好處就是讓你的類變成了模型。這意味着改變模型就只需要在一處(你自已的POCO類)中作出更改即可。
Code First, Database First, 和 Model First都只是一種建立可用於EF框架中以進行數據訪問的實體數據模型的方式。一旦模型構建,EF框架在運行時的表現是相同的,與如何構建模型無關。選擇從設計器開始還從代碼開始完全取決於您的選擇。圖1-1列出了不同的可選項。
微軟將Database First, Model First, Code First分別形成了可選的工作流程。這是因為每種選項都是一系列步驟,無論這些步驟是手工實施還是自動進行。例如,使用Database First工作流,你需要將數據庫轉化為工程,然后讓代碼生成器創建類。而Code First工作流則開始於類的代碼構建,然后用代碼生成所用的數據庫。
在.NET發行版本中間獲取Code First
Code First 在.Net4發布時尚未准備好。微軟不想將其在.Net5發布時才帶給開發者,就在2011年4月推出了"特別"版,稱為EF4.1.版本號隨后在更新時順延。2011年10月,EF4.2推出 ,替代了EF4.1,作為包含Code first的最新版本。核心API,System.Data.Entity.dll,仍然是.Net Framework的一部分,沒有與EF4.1,4.2緊密連接。EF4.2還包含了另一個重要的功能,稱之為DbContext API.DbContext是這個API的核心,除此之外還包含其他支持類。DbContext是一個輕量級的EF ObjectContext.這是一個包裝過的ObjectContext,只暴露了那些微軟認為大多數開發人員最常用的EF功能。DbContext也支持通過代碼模式訪問由ObjectContext所提供的復雜功能。DbContext提供了大量的通用任務,可以使開發者用較少的代碼完成這些任務,這在使用Code First時尤其明顯。由於微軟推薦使用DbContext來進行Code First操作,您會看到本書將貫穿始終介紹此方法。但是,另一本書,叫做Programming Entity Framework: DbContext,將深入探討DbContext,DbSet,驗證API以及DbContext帶來的其他特征。
圖1-2幫助你查看如何通過構建EF4 API核心添加Code First和DbContext功能。
靈活的發布日程
微軟將繼續通過VS提供的NuGet工具發布基於EF4.2框架的新功能。EF的核心庫將會隨着.Net的新版本的發布而內置在其中。但是諸如CodeFirst 和DbContext等核心功能會在EF的NuGet包中不斷得到更新。
先輸入代碼。。。
Code First 是個好聽的名字:先寫代碼,然后繼續。讓我們先看看一些基本的默認功能,不要管各種你可能會遇到的場景(后續的內容我們會專注於此)。
我們並不期望你重建本章的代碼案例。這些案例只是作一概覽,並不會深入。在第2章,你將會逐步深入。你將會跟隨我們在VS中的操作進行,然后嘗試你想實現的功能。
當然,首先需要一些代碼,以便足以描述業務域。本案例是關於一個寵物醫院收治患畜的業務模型。
Example 1-1 Domain classes
using System;
using System.Collections.Generic;
namespace ChapterOneProject
{
class Patient
{
public Patient()
{
Visits = new List<Visit>();
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime BirthDate { get; set; }
public AnimalType AnimalType { get; set; }
public DateTime FirstVisit { get; set; }
public List<Visit> Visits { get; set; }
}
class Visit
{
public int Id { get; set; }
public DateTime Date { get; set; }
public String ReasonForVisit { get; set; }
public String Outcome { get; set; }
public Decimal Weight { get; set; }
public int PatientId { get; set; }
}
class AnimalType
{
public int Id { get; set; }
public string TypeName { get; set; }
}
}
Code First 的核心是約定,這些默認的規則使我們可以用我們自己的類來創建模型。EF框架要求一個類必須有一個鍵屬性。規則約定如果一個屬性名為Id或者是類名+Id的形式(如PatientId),這一屬性就被自動配置為鍵。如果無法找到滿足這一規則的屬性,將會在運行時拋出一個異常告訴你沒有找到Key.其他約定包括確定字符串的默認長度,或者默認表結構,以及當類相互繼承時如何在數據庫內建表等等。
如果Code First完全依賴於這些規則去創建自己的類,我們需要做的工作是很有限的。但是Code First並不強制你按此要求設計類,這些規則的設計是為了Code First能夠自動處理一些通用的場景。如果你的類遵從這些規則,Code First不會需要更多信息。EF框架會以你的類直接工作。如果不遵從這些規則,你就必須通過Code First的一些配置選項來提供一些附加信息以確保你的類能夠被Code First 所理解。
I代碼1-1的三個類,Id屬性均符合鍵屬性的規則要求。使用這三個類按Code First工作不需要增加任何配置。
使用DbContext管理對象
前述的域與EF框架無關。Code First很美好,你直接就使用你自己的類。如果你從其他項目中現存的域類開始將獲得特別的好處。
使用Code First,您應該定義一個繼承自DbContext的類。此類的其中一個角色,應被指作為context,以便讓Code First知道有關類需要用於創建模型。這就是EF框架能夠感知類以及如何保持跟蹤這些類的原因。這需要引入另一個新類:DbSet。正如DbContext是一個簡單包裝的ObjectContext, DbSet是一個包裝過的EF4的ObjectSet對象,同樣簡化了正常使用ObjectSet的編碼任務量。
例1-2展示了這種context類的基本樣式。注意到其中有一個用於Patients 和Visits的DbSet屬性。DbSets允許你使用類型查詢。如果我們不希望直接查詢AnimalTypes,就不需要一個AnmialType的DbSet.Code First 足夠聰明知道Patient使用AnimalType類,會包含在模型內。
Example 1-2. VetContext class which derives from DbContext
using System.Data.Entity;
namespace ChapterOneProject
{
class VetContext : DbContext
{
public DbSet<Patient> Patients { get; set; }
public DbSet<Visit> Visits { get; set; }
}
}
使用數據層和域類
現在你可能會有一些驚訝:這些可以滿足數據層的需要了嗎?假如你100%按照Code First的約定就是這樣。
這里沒有數據庫連接字符串,甚至連數據庫也沒有。但你已經做好使用數據層的准備。例1-3顯示了一個創建新病患對象的方法。這個方法同時創建了該病患的信息以及第一次治療記錄;
然后我們將此實例裝入context,添加到DbSet<Patient>(Patinets)中(前已定義為context),最后調用DbContext的SaveChanges方法保存數據。
Example 1-3. Adding a patient to the database with the VetContext
private static void CreateNewPatient()
{
var dog = new AnimalType { TypeName = "Dog" };
var patient = new Patient
{
Name = "Sampson",
BirthDate = new DateTime(2008, 1, 28),
AnimalType = dog,
Visits = new List<Visit>
{
new Visit
{
Date = new DateTime(2011, 9, 1)
}
}
};
using(var context = new VetContext())
{
context.Patients.Add(patient);
context.SaveChanges();
}
}
記住目前還沒有任何連接字符串,也沒有任何數據庫。然后運行這些代碼后,我們會在本地的SQL Server Express找到名字與context類完全匹配的新的數據庫:ChapterOneProject.VetContext
有關此數據的結構圖示見圖1-3.
將數據庫構架與例1中定的類相對比,表與類,字段與屬性幾乎完全匹配,唯一的區別是外鍵,盡管在Patient類中並沒有外鍵屬性,但Patients.AnimalType_Id外鍵依然創建了。Code First得到此外鍵是根據類中表現出的關系確定的。(注意到Patient對AnimalType有一個引用),在數據庫中外鍵用來維持表間的關系。在這種處理關系的方面Code First提供了很多便利。在類中有很多方式來表達類間的關系。Code First能夠方便地識別這些關系。注意,PatientId字段,在類中本來是明確指向Visit類的屬性,是非空的,而來自於導航屬性的AnimalType_Id字段是可空的。約定再一欠確定了外鍵的可空性,如果想要修改這些字段,讓Code First按照您想法來表達,就需要使用附加的配置。
從類到數據庫
如果您在EF框架下工作過,一定會熟悉采用EDMX文件表達的可視化模型。你也可以意識到EDMX文件實際上就是一個XML文件,但是設計器使其工作更加容易。使用XML來描述模型需要專門的構架元素,沒有設計器處理XML源文件是相當耗費腦筋的。
很顯然顯示在設計器上的內容並非是XML文件內容的全部。這一文件中還有一些數據庫構架的描述,以使類能夠映射到類似圖1-3中相應的表和列中。模型XML,數據庫構架XML和映射XML都被引用為元數據。
在運行時,EF框架讀取前述三部分的XML文件並在內存中創建元數據的映像。內存中的影像不再是XML,而是強類型的對象,如EntityType, EdmProperty和ssociationType.EF框架與這種在內存中的類對象進行交互,然后在需要時再與數據庫進行交互。
Code First並沒有XML文件,其內存中的數據對象是根據您的域類集合直接生成的。這里是約定與配置在起作用。Code First有一個稱為DbModelBuilder的類。這個類讀取域中的類集合,然后構建內存模型。由於其也可以構建代表數據庫構架的元數據,因此可以用來創建數據庫。如果增加配置信息幫助模型構建器確定哪一個模型和數據庫構架應該看起來一致,模型構建器就會檢查這些類,並將有關信息集成進模型中,使數據庫構架與設想的一致。
圖1-4顯示了EF框架如何從代碼或XML文件(由設計器維護和操作)中創建內存模型。一旦內存模型得以構建,EF框架不再需要知道模型是如何創建的。也可使用內存的模型來確定數據庫架構是怎樣的,可以構建查詢訪問數據,將查詢結果返回對象,將對象的變化更新回數據庫。
使用配置
在某些場合下需要協助Code First理解你的意圖時,你有兩個選擇來實施配置:Data Annotations(數據注解)和Code First的Fluent API.根據個人喜好可以任選其一。有一些高級配置只有在使用Fluent API時才能實現。
Code First允許您配置各種屬性,關系,繼承,級別和數據庫映射。現在我們給出一個示例,先睹為快。本書將把精力和時間放在解釋約定和對你有用的配置屬性上去。
使用Data Annotations配置
多開發者喜歡使用Data Annotations配置方式,因為其非常簡單。Data Annotations將一些特性直接應用於想要影響的類或屬性上。這些特性可以在System.ComponentModel.DataAnnotations命名空間里找到。
例如,如果你想確保一個屬性應總是有一個值,你就可以使用Required特性。例1-4在AnimalType類的TypeName屬性上應用了這一特性。
Example 1-4. Using an annotation to mark a property as required
class AnimalType
{
public int Id { get; set; }
[Required]
public string TypeName { get; set; }
}
這會有兩個效果。第一個是數據庫中的TypeName字段將會設置為非空。第二個將會實現由EF框架提供的驗證,得益於EF4.1框架引入的驗證API。默認情況下,當保存更改時,EF框架將檢查確保被標記為Required的屬性是非空的。如果為空,將會拋出異常。
Required特性影響數據庫列的各個方面和屬性驗證。許多特性以用於數據庫映射。例如,Table特性告訴Code First該類映射到一個具有某個名子的表中。應用程序中的AnimailType數據完全可以存儲在稱為Species的表中。Table特性允許指定這種映射。
Example 1-5. Specifying a table name to map to
[Table("Species")]
class AnimalType
{
public int Id { get; set; }
[Required]
public string TypeName { get; set; }
}
使用Fluent API配置
使用Data Annotations非常簡單,在域中的類中指定元數據可能不適應你的開發風格。有一個替代的方法來添加配置,就是使用Code First's Fluent API.使用Fluent API,你的域類始終保持"清潔"。你的配置信息通過重寫DbContext暴露的方法OnModelCreating來實現。例1-6展示了與前述相同的配置,這個示例使用的是Fluent APIn。在每個配置中,代碼指出了model bulider如何配置AnimalType.
Example 1-6. Configuring the model using the Fluent API
class VetContext : DbContext
{
public DbSet<Patient> Patients { get; set; }
public DbSet<Visit> Visits { get; set; }
protected override void OnModelCreating
(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AnimalType>()
.ToTable("Species");
modelBuilder.Entity<AnimalType>()
.Property(p => p.TypeName).IsRequired();
}
}
第一個使用Fluent API的配置等價於Data Annotation 的Table特性,現在是ToTable方法,傳遞了一個表名指定了AnimalType類應該生成為該表;第二個配置使用了一個Lambda表達式指定AnimalType的一個屬性應用IsRequired方法。
這只是一個建立配置的方法。你將會在后續的章節中學到更多有關使用Data Annotations 和Fluent API的知識,用於配置屬性、關系,繼承,層級和數據庫映射等。
創建或指向數據庫
前面,你已經看到默認代碼創建了一個SQL Server Express數據庫。Code First完全自動調用了內置的連接字符串生成了數據庫。
你將在第6章全面了解Code First是如何與你的數據庫進行交互的。
本書中的示例將逐步介紹如何配置數據庫映射。這些概念同樣適用於生成數據庫和映射到一個已經存在的數據庫。當生成數據庫時,會影響生成的構架。當映射到存在的數據庫時,數據庫定義的構架必須與EF框架在運行時生成的保持一致。
在我們探索Code First的約定和配置時,我們也會允許Code First創建數據庫。這就使得運行應用程序的每個步驟都可以看到數據庫構架的變化。如果映射到一個已經存在的數據庫,唯一的區別是為數據庫指定Code First。最容易的辦法在"使用配置文件控制數據庫位置"(第6章),也可以看看"逆向Code First"(第8章)。
Code First 不支持的情況
Code First 是新加入到EF框架中的技術,有幾個特性目前尚不支持。EF開發團隊指出他們正在計划在未來版本中加入這些特性。
數據遷移(Database migrations)
在寫這本書的時候,Code First尚不支持數據遷移,換句話說,修改數據反映在模型的變化上。但是這一特性在不久的將來就可能發布。也可以在該團隊的blog上獲取有關數據遷移的早期預覽版。
映射到視圖
Code First目前只支持映射到表。這一不幸意味着不能將Code First直接映射到存儲過程,視圖或其他數據庫對象。如果你正使用Code First來生成數據庫,沒有辦法直接將這些特性加到數據庫,必須手工添加在Code First已經創建的數據里。如果映射到一個現存的數據庫,有一些其他的技術可以用來獲取數據庫中非表格數據。
這些技術描述為"映射到非表格數據庫對象",見第7章。
架構定義查詢
EF實體框架包含一個定義查詢功能,允許您指定數據庫查詢直接訪問XML元數據。還有一個查詢視圖功能,可讓您使用概念模型定義一個用來加載實體的查詢。這允許您指定的查詢提供獨立的數據庫。Code First不支持這些功能。
每類型多重實體(Multiple Entity Sets per Type (MEST))
Code First尚不支持MEST。MEST允許使用同一個類映射到不同的表中。這是EF構架的一個隱含特性,很少用到。EF團隊表示,為了確保Code First API的簡化,他們不打算加入對MEST的支持。
條件列映射
繼承層次結構時,Code First要求一個屬性始終具有相同的名稱映射到列,而不能實現條件列映射。例如,有一個Person基類具有一個NationalIdentifier屬性。American和Australian類都是派生自Person基類分別映射到數據庫中單獨的Americans和Australians表中。當使用設計器時,你可映射NationalIdentifier屬性到Americans表中的SSN列,映射到Australians表中的PassportNumber列。Code First 並不支持這種情況。映射到各個表中的NationalIdentifier必須具有同一名稱。
選擇Code First
現在你已經知道什么是Code First,你可能還在猶豫是否這是一種正確的應用程序開發模型工作流。好消息是這完全取決於你和你團隊的開發風格。
如果你喜歡書寫自己的POCO類然后使用代碼定義如何映射到數據庫,Code First是你要選擇的。正如前面提到的,Code First可以為你生成數據庫或者映射到已存在的數據庫;
如果你更喜歡用設計器定義你的類,並且用可視化的方式映射到數據庫,你可能不會使用Code First.如果你需要映射到一個現存的數據庫,你可能會想用Database First來從database逆向生成為模型。這可以使用VS的實體數據模型向導生成基於數據庫的EDMX文件。你可以使用設計器查看和編輯生成的模型。如果尚沒有數據庫而仍然要使用設計器,你應該使用設計器以Model First方式定義你的模型。根據創建的模型再創建數據庫。這些方法工作良好,在設計器中你會很好地應用你的類。
最后,如果你想針對現存的類使用EF框架,你可能會選擇Code First,即便你第一選擇可能是基於模型的設計器。如果選擇了設計器,你需要在設計器對模型和類作出調整。這種方式低效易出錯,而使用Code First可能會獲得好處。在Code First中,你的類就是模型,因此模型變更只需要在一處,不需要所謂的同步操作。
EF團隊正在對設計器進行改進以加入附加功能:數據庫逆向生成支持Code First和Fluent配置的 類。這一工具將為已經擁有數據庫但是更想使用Code First的開發者提供。你可以在第8章獲取更多信息。
EF工作流的選擇總結如圖1-5.
從本書中可以學到什么?
本書關注采用Code First構建和配置模型。這是一種采用EF框架編程的擴展,在本書背后你會找到很多參考,不會在這復制近900頁的有關EF框架的詳細信息,而是關注於如何實現查詢,更新,如何在各種應用類型中使用,如何自動測試,如何處理異常,安全,數據庫連接和事務。采用Code First 創建模型只是EF框架中諸多特性中的一個。事實上,當進入第二章,你會發現寵物醫院的域模型將會變為可編為應用程序的業務模型,稱之Break Away Geek Adventures(奇客冒險之旅)。另一本書名為《編程實體框架:DbContext》的書,將集中於DbContext,DbSet,驗證API,並使用NuGet包裝的Enfity Framework的一部分的功能,這些不是本書的重點。