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

MiroYuan發表於2015-09-14

 

摘要:

第一階段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 在蘇州高新區

完整目錄:

 

 

相關文章