原文: MVC5+EF6 入門完整教程11--細說MVC中倉儲模式的應用
摘要:
第一階段1~10篇已經覆寫了MVC開發必要的基本知識。
第二階段11~20篇将會側重于專題的講解,一篇文章解決一個實際問題。
根據園友的回報, 本篇文章将會先對呼聲最高的倉儲模式進行講解。
文章提綱
- 概述要點
- 理論基礎
- 詳細步驟
- 總結
設計模式的産生,就是在對開發過程進行不斷的抽象。
我們先看一下之前通路資料的典型過程。
在Controller中定義一個Context, 例如:
private AccountContext db = new AccountContext();
在Action中通路,例如擷取使用者清單:
var users=db.SysUsers;
類似于這種,耦合性太高。業務邏輯直接通路資料存儲層會導緻一些問題,如
重複代碼;不容易集中使用資料相關政策,例如緩存;後續維護,修改增加新功能不友善 等等。
我們使用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讓大家共享。
一、首先解決代碼備援的問題。
我們對ISysUserRepository和SysUserRepository 再進行一次抽象,去除repository class的備援。
仿照ISysUserRepository和SysUserRepository,建立IGenericRepository和GenericRepository
步驟和前面類似:
1. 建立泛型接口 IGenericRepository
下圖中右邊為IGenericRepository, 大家觀察下兩者的差別
2.建立對應的泛型倉儲類 GenericRepository
下面折疊起來的部分沒有任何變化。
3.修改UserController
把原來的注釋掉,給泛型類指定SysUser,主要更改部分如紅線表示。前端不用做任何更改。
運作Index就可以看到和之前一樣的結果了。
大家可以看到,通過泛型類已經消除了備援。
如果有其他實體隻需要改變傳入的TEntity就可以了,不需要再重新建立repository class
二、接下來解決第二個問題: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就可以看到和之前一樣的結果了。
三、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中倉儲模式的實作也有多種做法,本文介紹的微軟官方文檔提供的做法,個人覺得是比原生的做法更友善靈活,也更容易擴充。
感謝大家支援,祝學習進步。
PS.
另外公司研發部招聘工程師一名,主要研發資料可視化相關新産品,有興趣的可以部落格園短消息聯系我。
base 在蘇州高新區
完整目錄:
- @20150914
- MVC5+EF6 入門完整教程10:多對多關聯表更新&使用原生SQL @20150521
- MVC5+EF6 入門完整教程9:多表資料加載 @20150212
- MVC5+EF6 入門完整教程8 :不丢失資料進行資料庫結構更新 @20141215
- MVC5+EF6 入門完整教程7 :排序過濾分頁 @20141201
- MVC5+EF6 入門完整教程6 :分部視圖(Partial View) @20141117
- MVC5+EF6 入門完整教程5 :UI的一些改造 @20141113
- MVC5+EF6 入門完整教程4 :EF基本的CRUD @20141104
- MVC5+EF6 入門完整教程3 :EF完整開發流程 @20141027
- MVC5+EF6 入門完整教程2 :從前端UI開始 @20141021
- MVC5+EF6 入門完整教程1 :從0開始