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