在家閑著也是閑著,繼續(xù)寫我的[ASP.NET MVC 小牛之路]系列吧,。在該系列的上一篇博文中,,在顯示書本信息列表的時候,我們是在程序代碼中手工造的數(shù)據(jù),。本文將演示如何在ASP.NET MVC中使用Entity Framework Code First從數(shù)據(jù)庫中獲取數(shù)據(jù),。雖然本文題目聽上去比較簡單,但如果你認真閱讀,,相信你一定會有所收獲,。
ORM 和 EF當我們要開發(fā)一個應用程序,就要考慮怎樣展示數(shù)據(jù),,怎樣持久化數(shù)據(jù),。考慮這個問題時我們所要關心的東西,,最重要的莫過于程序的性能,、開發(fā)的簡易性和代碼的可維護、可擴展性,。
在應用程序中,,當要永久地保存數(shù)據(jù)時,,我們會選擇關系數(shù)據(jù)庫(Relation DataBase);當要臨時地保存數(shù)據(jù)時,,我們則使用存儲在內存中的對象,。目前對于大多數(shù)開發(fā)人員來說,都是用關系數(shù)據(jù)庫技術來作為持久化機制,。雖然現(xiàn)在有些人正在嘗試使用對象數(shù)據(jù)庫(Object DataBase)技術,,但關系數(shù)據(jù)庫技術在許多年以內依然會是最主要的持久化機制。
我們知道,,在關系數(shù)據(jù)庫技術中是用Table以行和列的結構來存放和組織數(shù)據(jù)的。在.NET 2.0以前,,C#還沒有泛型的時候,,人們基本上用填充在DataSet中的DataTable來映射并存放從關系數(shù)據(jù)庫中查詢出來的數(shù)據(jù),正如下面代碼所示: using (SqlConnection conn = new SqlConnection(connString)) { using (SqlDataAdapter da = new SqlDataAdapter("Select * from order", conn)) { DataTable dt = new DataTable(); da.Fill(dt); ... } } 這種方式雖然能讓面向對象語言匹配關系數(shù)據(jù)庫,,但它有一些明顯的缺點,,如非類型安全、難操控,、低性能等,。從.NET 2.0開始,人們開始通過泛型技術用實體模型對象的集合來匹配關系數(shù)據(jù)庫中的數(shù)據(jù),,這種方式解決了DataTable方式所面臨的缺點,,且它有強類型、在VS中自動完成,、編譯時檢查等特點,,廠受.Net開發(fā)人員的喜愛。 為了讓開發(fā)人員不用手動去做這種“匹配”工作,,人們研發(fā)了很多ORM工具(如Entity Framework,、NHibernate等)。ORM(Object Relation Mapping)工具,,顧名思義,,它的角色就是為了解決“關系”和“面向對象”之間的“失配”,它可以使得開發(fā)人員不用過多關心持久層而可以花更多的時間專注于業(yè)務,。 Entity Framework(EF)是微軟以ADO.NET為基礎所發(fā)展出來的ORM解決方案,,以Entity Data Model(EDM) 為主。EF利用了抽象化數(shù)據(jù)結構的方式,,將每個數(shù)據(jù)庫對象都轉換成應用程序中的類對象(Entity),,而數(shù)據(jù)字段都轉換為屬性 (Property),關系則轉換為結合屬性 (Association),,讓數(shù)據(jù)庫的 E/R 模型完全的轉成對象模型,,如此讓開發(fā)人員就能用熟悉的面向對象編程語言來調用訪問。EF 4.0 以后支持Database First,、Model First,、Code First三種生成模式,Code First模式用的人比較多,,本文的示例也將使用EF Code First模式,。 概念和理論性的東西就不多講了,,我也是帶著疑問去查找網(wǎng)絡資源根據(jù)自己的理解半摘半歸納出來的,而且鄙人不才,,很多東西想描述但都不知道怎么組織語言,。 本文目的主要是讓讀者對EF Code First有個感性的認識,然后了解EF Code First在ASP.NET MVC中的應用,,不會去研究原理性的東西,,下次有空再單獨介紹EF吧。 使用EF Code First接著上一篇博文[ASP.NET MVC 小牛之路]05 - 使用Ninject,,我們現(xiàn)在要把代碼中手工造的數(shù)據(jù)改成從數(shù)據(jù)庫讀取,。為此,我們先準備下數(shù)據(jù)庫,。本示例使用的是MS SQL Server,,使用其他數(shù)據(jù)庫也是一樣的。先創(chuàng)建一個名為BookShop的數(shù)據(jù)庫,,然后執(zhí)行下面的腳本創(chuàng)建一個Books表: CREATE TABLE Books ( [ID] INT NOT NULL PRIMARY KEY IDENTITY, [Title] NVARCHAR(100) NOT NULL, [Isbn] VARCHAR(20) NOT NULL, [Summary] NVARCHAR(1000) NOT NULL, [Author] NVARCHAR(50) NOT NULL, [Thumbnail] VARBINARY(MAX), [Price] DECIMAL(16, 2) NOT NULL, [Published] DATE NOT NULL, ) 然后隨便在表中加幾條用于測試的數(shù)據(jù): 接下來我們就要讓應用程序連接數(shù)據(jù)庫了,。由于上一篇博文是我在公司用休息的時間寫的,公司的電腦裝的是VS2010,,家里的筆記本裝的是VS2012,,所以得重新把上篇博文的示例移到VS2012上,對于本示例,,VS2010和VS2012都是一樣的,。上一篇示例項目的目錄結構如下: 本文的示例將在上篇的這個示例基礎上繼續(xù)。 用NuGet在BookShop.Domain工程中安裝Entity Framework包,,方法請參考本系列的上一篇文章,。 在BookShop.Domain工程的Concrete文件夾中添加一個名為EFDbContext的類,代碼如下: public class EFDbContext : DbContext { public DbSet<Book> Books { get; set; } } 使用EF Code First第一步就是創(chuàng)建一個繼承自System.Data.Entity.DbContext的類,,這個類將為數(shù)據(jù)庫中的每個表定義一個屬性,,屬性的名稱代表數(shù)據(jù)庫中的表名。DbSet作為返回類型,,它是用于生成CRUD(Create,、Read、Update和Delete)操作的裝置,,映射數(shù)據(jù)庫表的行,。 我們需要在BookShop.WebUI工程中的web.config配置文件中添加數(shù)據(jù)庫的連接字符串,來告訴EF怎樣連接數(shù)據(jù)庫,。根據(jù)自己機器上的數(shù)據(jù)庫配置連接字符串如下: <connectionStrings> <add name="EFDbContext" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=BookShop;User ID=sa;Password=sa" providerName="System.Data.SqlClient" /> </connectionStrings> 接下來,,我們把BookShop.Domain工程下Concrete文件中的BookRepository類文件改造一下,把代碼中手工造的數(shù)據(jù)改成從數(shù)據(jù)庫讀取,,以測試應用程序是否可以正常連接數(shù)據(jù)庫,。修改后的BookRepository類如下: public class BookRepository : IBookRepository { private EFDbContext context = new EFDbContext(); public IQueryable<Book> Books { get { return context.Books; } } } 在我們的這個倉儲類中,,我們改使用EF,通過創(chuàng)建一個EFDbContext類的實例來獲取數(shù)據(jù)庫中的數(shù)據(jù),。如你所見,,我們不需要自己寫ADO.NET代碼去連接和讀取數(shù)據(jù)庫,非常簡潔明了,,我們就是這樣使用Entity Framework的,。我們來看一下運行效果吧: 到這我們已經(jīng)成功使用EF連接上了數(shù)據(jù)庫,并從數(shù)據(jù)庫中讀取出來了數(shù)據(jù),。我們還可以通過Linq進行非常靈活的查詢,就像寫SQL一樣,。比如要查詢價格在100元以下的前10條記錄,,并且按價格從低到高顯示,那么我們可以在BookShop.WebUI工程下的BookController中的List方法中這樣寫: public ViewResult List() { return View(repository.Books .OrderBy(b => b.Price) .Where(b => b.Price < 100) .Take(10)); } 或許你很快就會對EF獲取數(shù)據(jù)庫的方式產(chǎn)生這樣的疑問:EF從數(shù)據(jù)庫中讀取整個Books表的數(shù)據(jù)到內存,,然后返回給調用者(上面代碼中的repository.Books)用Linq語句過濾用戶想要的前10條數(shù)據(jù),,如果Books表中有幾百萬條數(shù)據(jù),那內存豈不是完蛋了,,EF不會這么傻吧,?EF會不會根據(jù)Linq查詢語句智能地生成SQL文本再到數(shù)據(jù)庫中去查詢數(shù)據(jù)呢?這里就要講講IQueryable和IEnumerable了,。 IQueryable 和 IEnumerable其實,,對于上面的即有過慮又有排序的條件查詢Linq語句,EF是讀取數(shù)據(jù)庫中整個Books表中的數(shù)據(jù)到內存,,還是根據(jù)Linq查詢語句智能的生成SQL再執(zhí)行查詢,,完全編碼者來決定的。我們打開BookShop.Domain工程的BookRepository類文件,,請注意該類中Books屬性的返回類型: ... public IQueryable<Book> Books { get { return context.Books; } } 在上篇博文中,,我們對使用IQueryable作為返回類型提了個疑問:為什么用IQueryable而不用IEnumerable作為返回類型?答案是:使用IQueryable,,EF會根據(jù)調用者的Linq表達式先生成相應的SQL查詢語句,,然后到數(shù)據(jù)庫中執(zhí)行查詢,查詢出來的數(shù)據(jù)即是用戶想要的數(shù)據(jù),;而使用IEnumerable,,Linq表達式的過濾、排序等操作都是在內存中發(fā)生的,,即EF會先從數(shù)據(jù)庫中把整個表的數(shù)據(jù)查詢出來放在內存中,,然后由調用者使用Linq語句進行過濾、排序等操作,。是不是這樣呢,?我們來監(jiān)視一下兩種情況EF生成的SQL語句就知道了,。 我們先來看看使用IQueryable的情況。重新運行一下程序,,然后使用SQL Server Management Studio的活動和監(jiān)視器查看一下我們的BookShop應用程序所執(zhí)行的SQL語句,,結果如下:
結果證明使用IQueryable,EF是先根據(jù)Linq表達式生成相應的SQL語句再執(zhí)行查詢的,。 我們再稍稍修改一下代碼來看看用IEnumerable的情況,。把BookRepository類修改如下: public class BookRepository : IBookRepository { private EFDbContext context = new EFDbContext(); public IEnumerable<Book> Books { get { return context.Books; } } } 當然BookRepository類所實現(xiàn)的IBookRepository接口(在BookShop.Domain工程的Abstract文件夾中)也要改一下: public interface IBookRepository { IEnumerable<Book> Books { get; } } 再重新運行一下應用程序,用活動和監(jiān)視器查看最后執(zhí)行的SQL語句如下圖: 我們看到改用IEnumerable后,,EF生成的SQL沒有任何過濾,、排序等的操作,它一次把表中的所有數(shù)據(jù)都Select出來,,和上面寫的Linq表達式一點都沒關系,。 IQueryable雖然可以很智能地根據(jù)Linq表達式生成相應的SQL語句,但畢竟有一個分析Linq表達式的過程,,相對來說性能比IEnumerable要差,。那么我們什么時候用IEnumerable,什么時候用IQueryable呢,?我想,,對于少量的數(shù)據(jù)(比如從數(shù)據(jù)庫中讀取應用程序相關的系統(tǒng)信息)和不需要對數(shù)據(jù)進行過濾操作的情況,用IEnumerable比較適合,;對于數(shù)據(jù)量較大需要對數(shù)據(jù)進行過濾(比如分頁查詢)的情況,,則用IQueryable比較合適。 |
|
來自: 賈朋亮博客 > 《MVC 小牛之路》