細說MVC中倉儲模式的應用
文章提綱
- 概述要點
- 理論基礎
- 詳細步驟
- 總結
概述要點
設計模式的產生,就是在對開發過程進行不斷的抽象。
我們先看一下之前訪問數據的典型過程。
在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. 按姓名排序
好了,到目前為止,應該接近完美了,數據層的問題應該可以解決一半了。
總結
首先將Controller和Context之間抽象出一層來專門負責數據訪問。
然後進行第二次抽象,將共有的方法進行泛化,提取出一個GenericRepository出來,將每個具體的類型放到UnitOfWork中進行統一處理。
最後改造了查詢方法,通過傳入表達式可以根據條件靈活返回查詢結果。
需要說明的是,EF本身的設計其實就是repository+unit of work , 如果是簡單應用直接用原來的做法就可以了。
另外MVC中倉儲模式的實現也有多種做法,本文介紹的微軟官方文檔提供的做法,個人覺得是比原生的做法更方便靈活,也更容易擴展。
感謝大家支持,祝學習進步。
http://www.cnblogs.com/miro/p/4806199.html#!comments
細說MVC中倉儲模式的應用