這是上周就寫好的文章,是在公司浩哥的建議下寫的,本來是部門里面分享求創新用的,這里貼出來分享給大家。
最近在對MVC的學習過程中,接觸到了Code First這種新的設計模式,感覺很新穎,並且也體驗到了這種方式所帶來的便利。這里將我的一些理解分享出來。
需要了解的概念
POCO
POCO(Plain Old CLR Object)的概念是從java的POJO借用而來,而兩者的含義是一致的,不同的僅僅是使用的語言不一樣。所以POCO的解釋就是“Plain Old C# Object”。POJO的內在含義是指那些沒有從任何類繼承、也沒有實現任何接口,更沒有被其它框架侵入的對象。
PO
PO是指持久對象(persistant object持久對象)。持久對象實際上必須對應數據庫中的entity,所以和POJO有所區別。比如說POJO是由new創建,由GC回收。但是持久對象是 insert數據庫創建,由數據庫delete刪除的。基本上持久對象 生命周期和數據庫密切相關。另外持久對象往往只能存在一個數據庫 Connection之中,Connnection關閉以后,持久對象就不存在了,而POJO只要不被GC回收,總是存在的。
ORM
ORM(Object/Relational Mapping) 對象關系映射,主要是把數據庫中的關系數據映射稱為程序中的對象
NHibernate
NHibernate是一個面向.NET環境的對象/關系數據庫映射工具。對象/關系數據庫映射(object/relational mapping,ORM)這個術語表示一種技術,用來把對象模型表示的對象映射到基於SQL的關系模型數據結構中去。所以NHibernate與Entity Framework是很相近的。
Entity Framework
Entity Framework的全稱是ADO.NET Entity Framework,是微軟開發的基於ADO.NET的ORM(Object/Relational Mapping)框架。
其架構圖如下:

在接手一個新項目時我們所熟知及習慣使用的設計方式,是在分析需求后開始創建數據庫中的表,一旦表確定后變動不會太大或者幾乎不再去更改表的結構,后面的模型編寫及業務邏輯的編寫都在這個基礎上進行。這種方式稱為Database First。
而在2011四月發布的Entity Framework 4中存在三種工作方式,他們分別為:Code First, Model First和Database First。其中本文要討論的Code First就是新增的一種方式。
Code First
為了支持以設計為中心的開發流程,EF4 還更多地支持以代碼為中心 (code-centric) ,我們稱為代碼優先的開發,代碼優先的開發支持更加優美的開發流程,它允許你:
在不使用設計器或者定義一個 XML 映射文件的情況下進行開發。
·允許編寫簡單的模型對象POCO (plain old classes),而不需要基類。
·通過"約定優於配置",使得數據庫持久層不需要任何的配置
·也可以覆蓋"約定優於配置",通過流暢的 API 來完全定制持層的映射。
Code First是基於Entity Framework的新的開發模式,原先只有Database First和Model First兩種。Code First顧名思義,就是先用C#/VB.NET的類定義好你的領域模型,然后用這些類映射到現有的數據庫或者產生新的數據庫結構。Code First同樣支持通過Data Annotations或fluent API進行定制化配置。
其他兩種設計方式
Database First是最老也是應用得最廣泛的一種設計方式。如上文提到過的那樣,Database First這種方式的設計高度依賴於數據庫中表的結構,根據表及表間的關系來創建模型。如果后期需求有所變更或者功能有很大變化的話,需要涉及到更改數據庫所付出的代價將會很大,因為之前編寫好的代碼將不再適用於新的表,我們必需重構以更改代碼中的邏輯以適應更改之后的表。
Model Firstj是創建ADO.NET實體對象以及它們之間的關系,然后再指定到數據庫的映射。這個實體對象即為Model。
在MVC中使用CodeFirst
這里我將用一個非常簡單的例子來演示一下Code First在MVC中的使用,主要還是展示Code First 具體是怎么工作的。
Note:你可能需要單獨安裝Entity Framework 4.1
ADO.NET Entity Framework 4.1 安裝文件下載地址:http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=8363
下面我們開始這個例子。既然是Code First,就是以代碼為中心進行設計,所以我們並不關心具體的數據庫構造到底是怎么樣的。我們從分析需求開始,即我們要實現的功能。這里我僅僅是要實現一個展示博客列表的頁面,這里我們不關注CRUD的實現,因為這些都可以在MVC里自動生成。這里我們將看到,整個過程我們沒有寫sql語句,也沒有用sql server management studio 在數據庫里進行表的設計,但我們運行程序時,這些需要的表就已經自動創建好了,並且我們在頁面進行CRUD操作時,也是與數據庫里面的數據是完全同步的。
打開VS2010新建一個MVC3項目,注意環境要設為.Net Framework 4。這里我將項目命名為MvcBlog。

選擇空的模板,View engine我們用ASPX。

因為Code First需要EntityFramework支持,項目創建好后在引用里面系統已經自動為我們引用了EntityFramework程序集,如果沒有需要手動引用進來。

(為了展示完整的目錄層結構,在圖片上我把中間很多引用去掉換以省略號代替,所以你看到這張圖片時不要感到奇怪,我的VS不是特別定制版的)
我們首先在Model中創建一個博客類來保存一篇博客的基本信息,它包含一個博客標識BlogId, 標題Title還有創建日期CreateDate。
在Model文件夾上右擊選擇Add->Class。

在Name中輸入Blog再點擊Add。

其中的代碼如下:
1 namespace MvcBlog.Models 2 3 { 4 5 public class Blog 6 7 { 8 9 public int BlogId { get; set; } 10 11 public string Title { get; set; } 12 13 14 15 public DateTime CreateDate { get; set; } 16 17 } 18 19 }
同樣的方法我們再新建一個類,命名為BlogEntities.cs,這個類跟Blog類的功能是不同的,我們將在代碼里將它定義為從DBContex繼承,這樣這個BlogEntities上下文類將扮演着與數據庫溝通的重要角色,在這里面可以定義需要的表,項目運行后這些表將自動在數據庫創建。這里,我們用剛才定義好的Blog來定義一個Dbset,代碼如下:
1 public class BlogEntities:DbContext 2 3 { 4 5 public DbSet<Blog> Blogs { get; set; } 6 7 }
(注意:這里需要在文件頂部添加using System.Data.Entity引用語句才能使用DbContext及DbSet)
上面我們把模型准備好了,現在定義Controller。但在進行這一步前,我們需要Ctrl+Shift+B編譯一下程序,以便系統知道我們之前定義好的Blog類和BlogEntities類。右擊Controllers文件夾選擇Add->Controller打開添加Controller對話框,將其命名為HomeController(MVC的Controller,當然你也可以隨便命名,只不過在打開網頁時需要手動輸入路徑),此刻數據庫還沒有被創建,並且呆會自動創建后里面也是空的沒有數據,我們需要向里面添加一些數據,所以這里模板我們選擇帶有CRUD(增刪除改查)功能的,再將下面的模型類選為Blog,上下文類選為BlogEntities,系統會自動實現CRUD操作,不用我們手動編寫。
(注意:之前必需編譯一下,才能在這一步的下拉菜單里找到我們已經定義好的類)

單擊Add之后,生成的代碼大概是這樣:
namespace MvcBlog.Controllers
{
public class HomeController : Controller
{
private BlogEntities db = new BlogEntities();
//
// GET: /Home/
public ViewResult Index()
{
return View(db.Blogs.ToList());
}
//
// GET: /Home/Details/5
public ViewResult Details(int id)
{
Blog blog = db.Blogs.Find(id);
return View(blog);
}
//
// GET: /Home/Create
public ActionResult Create()
{
var blog = new Blog();
return View();
}
//
// POST: /Home/Create
[HttpPost]
public ActionResult Create(Blog blog)
{
if (ModelState.IsValid)
{
db.Blogs.Add(blog);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(blog);
}
//
// GET: /Home/Edit/5
public ActionResult Edit(int id)
{
Blog blog = db.Blogs.Find(id);
return View(blog);
}
//
// POST: /Home/Edit/5
[HttpPost]
public ActionResult Edit(Blog blog)
{
if (ModelState.IsValid)
{
db.Entry(blog).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(blog);
}
//
// GET: /Home/Delete/5
public ActionResult Delete(int id)
{
Blog blog = db.Blogs.Find(id);
return View(blog);
}
//
// POST: /Home/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Blog blog = db.Blogs.Find(id);
db.Blogs.Remove(blog);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
視圖已經自動添加在了Views文件夾下,到此我們的程序已經能夠工作了,雖然我們似乎什么都沒做。在運行前我們可以打開Sql server management studio (或者在VS里用服務器管理器連接到你本地的數據庫)查看一下以確定數據庫里還沒有我們程序里需要的Blog表。然后我們Ctrl+F5運行網站。

頁面顯示出了我們在Blog中定義的Title字段和CreateDate字段,但由於還沒有數據來進行顯示,所以只有標題。
這個時候,我們斷開數據庫再重新連接,就會發現EF創建了一個程序命名空間為名稱的數據庫MvcBlog.Models.BlogEntities,里面包含我們用Dbset定義的Blogs表。表中的列也正好與我們在Blog類中定義的字段相對應。

仔細觀察你會發現,它自動將BlogId定義為了主鍵,這是EF的convention在起作用,如果我們沒有顯示地指定數據庫的連接字符串等配置信息,這些值將會遵從EF里的約定進行取值,比如這里的數據庫名 MvcBlog.Models.BlogEntities。
這樣的名稱當然不是我們想要的,而且,如果我們本地安裝了多個數據庫實例的話,我們也希望指定項目將數據創建在我們想要的實例當中。要覆蓋默認的約定,我們只需在Web.config(注意:不是Views文件夾下的Web.config)里添加一下對數據庫的配置信息,代碼如下:
<connectionStrings> <add name="BlogEntities" connectionString="server=(local)\sqlexpress;database=MvcBlog;integrated security=true;" providerName="System.Data.SqlClient"/> </connectionStrings>
注意上面的name應和Models中的定義的上下文類名稱相同,這樣才能使EF正常工作。server換成你本機數據庫實例的名字。現在我們可以看到在先前那個數據庫上方它生成了我們重新命名的數據庫。

重新運行程序,我們在頁面中點擊Create New來添加幾條數據。

添加完數據后我們再返回去看數據庫中的變化。

如圖,數據庫中已經保存了我們在頁面上添加的數據。
到這里大家已經看到了Code First 設計方式的大概過程。現在我們來回顧一下,之前所做的操作,將會更加明白這一過程是怎樣進行的,特別是代碼優先是如何做到在沒有數據庫支持的情況下先建立數據模型,然后再對數據庫進行操作的。
首先我們在Models中創建了所需要的數據模型Blog類,里面包含的字段將映射到以后數據庫表中的相應列。
接下來同樣是在Models文件夾中,我們定義了一個最關鍵的BlogEntities上下文類,它繼承自System.Data.Entity下的DbContext,它將我們的數據模型映射到數據庫中,將代碼中的數據持久化。
最后,在頁面上進行添加數據時,頁面將表單數據通過Blog類型傳回Controller里面相應的方法,這里是處理Post回傳的Create Action,它接收傳回來的數據,通過調用db.SaveChanges()進行了數據的保存。這里db是BlogEntities上下文類的一個實例。通過BlogEntities上下文類,我們進行的RUD操作將反應到數據庫中,完成了從代碼到數據庫的更新過程。
Data Annotations
接着上面的例子,試想,一篇博客一般還包含有分類信息,比如日志,隨筆等。現在我們就去添加這樣一個類,保存博客的分類信息,相應地,我們還應更新一下我們的博客類,讓它包含一個分類屬性,這樣我們就可以指定一篇博客的所屬分類了。
這里將看到如何通過Code First創建表間的外鍵關系約束,以及定義表中列的其他信息,比如顯示的信息,
我們在Models文件夾中繼續添加一個名為Category的類,它包含一個分類標識屬性CategoryId, 一個分類名屬性CategoryName。代碼如下:
1 namespace MvcBlog.Models 2 3 { 4 5 public class Category 6 7 { 8 9 public int CategoryId { get; set; } 10 11 public string CategoryName { get; set; } 12 13 } 14 15 }
更新Blog類:
1 namespace MvcBlog.Models 2 3 { 4 5 public class Blog 6 7 { 8 9 public int BlogId { get; set; } 10 11 public string Title { get; set; } 12 13 public DateTime CreateDate { get; set; } 14 15 public int CategoryId { get; set; } 16 17 } 18 19 }
我們向其中添加了一個CategoryId屬性,下面設置它為引用自Category中CategoryId的外鍵。
這里有兩個方法Data Annotation 和 Fluent API用於實現這種到數據庫的映射,這里只討論Data Annotation。
由於CategoryId將會成為Blog到Category的導航屬性,所以,除了要在Blog中添加CategoryId外,還需要添加一個類型為Category的屬性,這樣,在Blog中才會有一個來自Category中CategoryId的聲明。
1 public class Blog 2 3 { 4 5 public int BlogId { get; set; } 6 7 public string Title { get; set; } 8 9 public DateTime CreateDate { get; set; } 10 11 public int CategoryId { get; set; } 12 13 public Category Category { get; set; } 14 15 }
在使用Data Annotation添加外鍵屬性前需要添加System.ComponentModel.DataAnnotations命名空間到Blog類。然后我們就可以在CategoryId上面添加一個外鍵屬性了。更改后的代碼如下 :
public class Blog { public int BlogId { get; set; } public string Title { get; set; } public DateTime CreateDate { get; set; } [ForeignKey("CategoryId")] public int CategoryId { get; set; } public Category Category { get; set; } }
同時更新我們的BlogEntities對象:
1 public class BlogEntities:DbContext 2 3 { 4 5 public DbSet<Blog> Blogs { get; set; } 6 7 public Category Categories { get; set; } 8 9 }
這時當我們運行程序時,會報錯,如圖:

因為之前運行程序時已經創建了數據庫了,而現在我們在對模型進行更改后,它無法完全將更改之后的模型映射到之前的數據庫,所以會出錯。從錯誤提示中已經給出了解決辦法。要么手動刪除之前創建好的數據庫,要么使用DropCreateDatabaseIfModelChanges 的一個實例來對數據庫進行初始化。需要注意的一點是,在商業開發中,第二種方法要小心使用,因為它會把之前的數據庫自動刪掉重新創建,而如果你之前保存有大量信息在里面的話將無法挽回。
這里我們不想每次在修改模型之后都手動去刪除,所以用第二種方法將十分簡便,只需到Global.asax文件的Application_Start()方法里面添加如下一行即可。
1 protected void Application_Start() 2 3 { 4 5 Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BlogEntities>()); 6 7 AreaRegistration.RegisterAllAreas(); 8 9 10 11 RegisterGlobalFilters(GlobalFilters.Filters); 12 13 RegisterRoutes(RouteTable.Routes); 14 15 }
這樣,模型改變之后,它會刪除掉原來的數據庫重新創建。
我們再次運行程序,然后再去數據庫看,第二張表Category已經添加進去了,並且在Blog表里面,也出現了一個CategoryId外鍵,正如我們所想要的那樣。

需要注意的地方
一個就是性能問題,使用edm designer從數據庫來生成的ObjectContext,它會把很多東西存在CSDL,MSL,SSDL文件里,比如一些mapping信息,當頁面load時需要先從EDM文件里來load這些metadata,而使用Code-first是從Assembly里load數據,這樣應該會節省不少時間。但另一方面,EF自動生成的CONTEXT實體類會附帶很多冗余信息,使得文件相對臃腫,在讀取實體類中的信息時會在搜索上耗費不必需的時間。關於性能問題具體可閱讀一下參考中的第一篇文章。
另外就是現在Code First應用得不是很多,就現在學習中碰到的例子來看,大部分都是Database First方式的。由於這是伴隨微軟新一代的EF4.1推出的設計方式,必定有其優越性,所以以后的項目中還是可以嘗試用這種方式來進行設計。
后記
我也是初學者,研究得不是很深刻,借鑒了許多別人的經驗與分享,再加上自己的創新理解,肯定有不恬當的地方,權當加深自己的理解,與大家共同學習。
可以參考的一些文章
1.WCF和Entity framework 發現的性能問題
http://www.cnblogs.com/GaryChen/archive/2010/02/06/1664912.html
2.Entity Framework 4中的Code-First, Model-First和Database-First模式
http://www.cnblogs.com/n-pei/archive/2010/08/12/1797751.html
3.Code-First Development with Entity Framework 4
4.ADO.NET Entity Framework
http://en.wikipedia.org/wiki/ADO.NET_Entity_Framework
5.A Code First Example using Entity Framework 4
http://chris.widdowson.id.au/?p=746#
6. Entity Framework At-a-Glance
http://msdn.microsoft.com/en-us/data/aa937709
7. Nadege Deroussen在codeproject的三篇文章
Entity Framework Code First: Let's Try It
http://www.codeproject.com/Articles/318010/Entity-Framework-Code-First-Let-s-Try-It
EF Code First: Add a Foreign Key relationship
http://www.codeproject.com/Articles/319366/EF-Code-First-Add-a-Foreign-Key-relationship
EF Data Annotations and Code Fluent
http://www.codeproject.com/Articles/368164/EF-Data-Annotations-and-Code-Fluent
