MVC5+EF6 入門完整教程十一:細說MVC中倉儲模式的應用

摘要:html

第一階段1~10篇已經覆蓋了MVC開發必要的基本知識。前端

第二階段11~20篇將會側重於專題的講解,一篇文章解決一個實際問題。數據庫

根據園友的反饋, 本篇文章將會先對呼聲最高的倉儲模式進行講解。設計模式

文章提綱

  • 概述要點
  • 理論基礎
  • 詳細步驟
  • 總結

概述要點

設計模式的產生,就是在對開發過程進行不斷的抽象。緩存

咱們先看一下以前訪問數據的典型過程。架構

在Controller中定義一個Context, 例如:框架

private AccountContext db = new AccountContext();單元測試

在Action中訪問,例如獲取用戶列表:測試

var users=db.SysUsers;this

 

相似於這種,耦合性過高。業務邏輯直接訪問數據存儲層會致使一些問題,如

重複代碼;不容易集中使用數據相關策略,例如緩存;後續維護,修改增長新功能不方便 等等。

 

咱們使用repository來將業務層和數據實體層分開來,業務邏輯層應該對組成數據源層的數據類型不可知,好比數據源多是數據庫或者Web service

在數據源層和業務層之間增長一個repository層進行協調,有以下做用:

1.從數據源中查詢數據

2.映射數據到業務實體

3.將業務實體數據的修改保存到數據源 (持久化數據)

這樣repository就將業務邏輯和基礎數據源的交互進行了分隔。

數據和業務層的分離有以下三個優勢:

1.集中管理不一樣的底層數據源邏輯。

2.給單元測試提供分離點。

3.提供彈性架構,總體設計能夠適應程序的不斷進化。

咱們將會對原有作法進行兩輪抽象,實現咱們想要的效果。

 

理論基礎

倉儲和工做單元模式是用來在數據訪問層和業務邏輯層之間建立一個抽象層。

應用這些模式,能夠幫助用來隔離你的程序在數據存儲變化。

 

 

下圖比較了不使用庫模式和使用庫模式時controller和context 交互方式的差別。

說明:庫模式的實現有多種作法,下圖是其中一種。

 

 

準備工做

首先咱們先搭建好空的框架,準備基本的結構和一些測試數據。

咱們再也不在第一階段的MVCDemo上進行更改了, 從新創建一個新項目XEngine做爲咱們第二階段的演示項目 。

1.新建項目

 

2.新建Model

咱們新建 SysUser, SysRole , SysUserRole

 

3. 安裝EF, 準備測試數據

a.安裝EF

b.新建文件夾DAL

à 新建類 XEngineContext.cs

à 新建類 XEngineInitializer.cs

 

c.修改HomeController.cs,運行Index視圖,來生成數據庫結構和測試數據。

說明:準備工做有不清楚的請參考第三篇文章:

http://www.cnblogs.com/miro/p/4053473.html

 

至此,準備工做已經OK,下面就看看如何運用倉儲模式來改造咱們的項目。

 

詳細步驟

整個過程分紅兩輪抽象。

第一輪抽象 : 解耦Controller和數據層

對每個實體類型創建一個對應的倉儲類。

以SysUser來講,新建一個倉儲接口和倉儲類。

在controller中經過相似於下面這種方式使用:

ISysUserRepository sysUserRepository = new SysUserRepository();

 

下面來看建立 SysUser 倉儲類具體作法:

1.新建個文件夾 Repositories, 後面新建的倉儲類都放在這個文件夾中

 

2.建立接口 ISysUserRepository

接口中聲明瞭一組典型的CRUD方法。

其中查找方法有兩個:返回所有和根據ID返回單個。

 

 

 

3.建立對應的倉儲類 SysUserRepository

建立類 SysUserRepository, 實現接口 ISysUserRepository

a. 把接口中的方法所有實現

 

b.關閉鏈接

 

說明:

GC.SuppressFinalize(this);

由於對象會被Dispose釋放,因此須要調用GC.SuppressFinalize來讓對象脫離終止隊列,防止對象終止被執行兩次。

 

 

 

 

 

 

 

4.Controller中使用SysUser倉儲類

咱們新建個Controller : UserController

用 List 模板生成視圖。

 

修改Controller以下:

 

運行Index就能夠看到用戶列表了。

 

 

 

更新和刪除就再也不舉例了,都比較簡單,你們能夠本身去試驗,方法相似。

至此,第一次抽象就完成了。

能夠看到,咱們增長了一個抽象層,將數據鏈接的部分移到Repository中去,這樣實現了Controller和數據層的解耦。

 

觀察一下能夠發現,還存在兩個問題:

1.若是一個controller中用到多個repositories,每一個都會產生一個單獨的context

2.每一個entity type 都要實現一個對應的repository class ,這樣會產生代碼冗餘。

下面咱們就再進行一次抽象,解決這兩個問題。

說明:

爲方便講述,實體類型 和 倉儲類 如下直接用 entity type 和 repository class表示。

 

 

 

第二輪抽象:經過泛型消除冗餘的repository class

爲每一個 entity type 建立一個repository class 會

a. 產生不少冗餘代碼

b. 會致使不一致地更新

舉例:

你要在一個 transaction中更新兩個不一樣的 entity type

若是使用不一樣的context instance, 一個可能成功,另一個可能失敗。

 

咱們使用 generic repository去除冗餘代碼

使用unit of work保證全部repositories使用同一個 context

說明:

後面將會新建一個unit of work class 用來協調多個repositories工做, 經過建立單一的context讓你們共享。

 

 

1、首先解決代碼冗餘的問題。

咱們對ISysUserRepository和SysUserRepository 再進行一次抽象,去除repository class的冗餘。

 

仿照ISysUserRepository和SysUserRepository,新建IGenericRepository和GenericRepository

步驟和前面相似:

1. 建立泛型接口 IGenericRepository

下圖中右邊爲IGenericRepository, 你們觀察下二者的區別

2.建立對應的泛型倉儲類 GenericRepository

下面摺疊起來的部分沒有任何變化。

 

3.修改UserController

把原來的註釋掉,給泛型類指定SysUser,主要更改部分如紅線表示。前端不用作任何更改。

 

運行Index就能夠看到和以前同樣的結果了。

 

你們能夠看到,經過泛型類已經消除了冗餘。

若是有其餘實體只須要改變傳入的TEntity就能夠了,不須要再從新建立repository class

 

2、接下來解決第二個問題:context的一致性。

咱們在DAL文件夾中新建一個類UnitOfWork用來負責context的一致性:

當使用多個repositories時,共享同一個context

咱們把使用多個repositories的一系列操做稱爲一個 unit of work

當一個unit of work完成時,咱們調用context的SaveChanges方法來完成實際的更改。因爲是同一個context, 全部相關的操做將會被協調好。

這個類只須要一個Save方法和一組repository屬性。

每一個repository屬性返回一個repository實例,全部這些實例都會共享一樣的context.

把 GenericRepository.cs 中的Save 和 Dispose 刪除, 移到UnitOfWork中。

將IGenericRepository 中的IDisposable接口繼承也去掉.

Save & Dispose 的工做統一在UnitOfWork中完成。

 

 

在UserController中使用UnitOfWork, 修改以下:

前端一樣不用作任何更改,運行Index就能夠看到和以前同樣的結果了。

 

3、And one more thing

前面已經將代碼冗餘和context不一致的問題全都解決了。

不過還有個問題。

你們看咱們的查詢方法:

IEnumerable<TEntity> Get();

TEntity GetByID(object id);

上面的方法一個是返回全部結果,一個是根據id返回單筆記錄。

在實際應用中,有個問題確定會遇到:

須要根據各類條件進行查詢。

好比 查詢用戶, 要實現相似 GetUsersByName 或者 GetUsersByDescription 等等。

解決這個問題經常使用的一種作法是:

建立一個繼承類,針對特定的entity type 添加 特定的Get方法,好比前面說的 GetUsersByName.

這樣作有一個缺點,會產生冗餘代碼。特別是在一個複雜程序中,可能會產生大量的繼承類和特定的方法,維護起來又須要花費不少工做。咱們不用這種作法。

 

咱們使用表達式參數的方法。

改造一下Get方法。

先分析一下需求,經常使用的有三點:

1. 過濾條件

2. 排序

3. 須要Eager loading 的相關數據

針對這三點,咱們給Get加入三個可選參數

再來看下GenericRepository 中的實現

 

最後修改下Index方法作測試:

a. 不加入任何條件

b. 加入過濾條件,選出張三

 

c. 按姓名排序

好了,到目前爲止,應該接近完美了,數據層的問題應該能夠解決一半了。

 

總結

本篇是MVC系列文章,第二階段專題篇的第一篇。

首先將Controller和Context之間抽象出一層來專門負責數據訪問。

而後進行第二次抽象,將共有的方法進行泛化,提取出一個GenericRepository出來,將每一個具體的類型放到UnitOfWork中進行統一處理。

最後改造了查詢方法,經過傳入表達式能夠根據條件靈活返回查詢結果。

須要說明的是,EF自己的設計其實就是repository+unit of work , 若是是簡單應用直接用原來的作法就能夠了。

另外MVC中倉儲模式的實現也有多種作法,本文介紹的微軟官方文檔提供的作法,我的以爲是比原生的作法更方便靈活,也更容易擴展。

相關文章
相關標籤/搜索