[ASP.NET MVC 小牛之路]06 - 使用 Entity Framework

在家閒着也是閒着,繼續寫個人[ASP.NET MVC 小牛之路]系列吧。在該系列的上一篇博文中,在顯示書本信息列表的時候,咱們是在程序代碼中手工造的數據。本文將演示如何在ASP.NET MVC中使用Entity Framework從數據庫中獲取數據。雖然本文題目聽上去比較簡單,但若是你認真閱讀,相信你必定會有所收穫。html

本文目錄:web

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開發人員的喜好。app

爲了讓開發人員不用手動去作這種「匹配」工做,人們研發了不少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 FirstModel FirstCode First三種生成模式,Code First模式用的人比較多。ide

概念和理論性的東西就很少講了,我也是帶着疑問去查找網絡資源根據本身的理解半摘半概括出來的,並且鄙人不才,不少東西想描述但都不知道怎麼組織語言。

本文目的主要是讓讀者對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/

相關文章
相關標籤/搜索