細說MVC中倉儲模式的應用

来源:https://www.cnblogs.com/frank0812/archive/2018/10/24/9841460.html
-Advertisement-
Play Games

文章提綱 概述要點 理論基礎 詳細步驟 總結 概述要點 理論基礎 詳細步驟 總結 概述要點 設計模式的產生,就是在對開發過程進行不斷的抽象。 我們先看一下之前訪問數據的典型過程。 在Controller中定義一個Context, 例如: private AccountContext db = new ...


文章提綱

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

概述要點

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

我們先看一下之前訪問數據的典型過程。

在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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 設計模式:觀察者模式 當一個對象的狀態發生改變時,依賴他的對象會全部收到通知,並自動更新。 使用場景 一個事件發生後,要執行一連串更新操作。傳統的編程方式,就是在事件的代碼之後直接加入處理邏輯,當更新得邏輯增多之後,代碼會變得難以維護,這種方式是耦合的,侵入式的,增加新的邏輯需要改變事件主題的代碼。 ...
  • 緩存穿透 概念 訪問一個不存在的key,緩存不起作用,請求會穿透到DB,流量大時DB會掛掉。 解決方案 1. 採用布隆過濾器,使用一個足夠大的bitmap,用於存儲可能訪問的key,不存在的key直接被過濾; 2. 訪問key未在DB查詢到值,也將空值寫進緩存,但可以設置較短過期時間。 緩存雪崩 概 ...
  • 下麵總結並演示了 Redis 的 常用管理命令、key 操作、字元串、集合、列表、散列類型的操作命令。 你需要掌握的 Redis 知識 "史上最全 Redis 高可用解決方案總結" "為什麼分散式一定要有Redis?" "Spring Boot Redis Cluster 實戰乾貨" "Spring ...
  • 系統介紹: 1.系統採用主流的 SSM 框架 jsp JSTL bootstrap html5 (PC瀏覽器使用) 2.springmvc +spring4.3.7+ mybaits3.3 SSM 普通java web(非maven, 附贈pom.xml文件) 資料庫:mysql 3.開發工具:my ...
  • GraphQuery " " " " " " " " " " " " GraphQuery is a query language and execution engine tied to any backend service. It is . Project Address: "GraphQue ...
  • Learun FrameWork 強大工作流引擎,讓OA更智能 互聯網的發展促使企業在信息化的道路上不斷探索,而隨著企業信息化進程的不斷深入,OA協同辦公的概念也逐步進入大眾的視野。 OA的選型關乎企業的生存發展,除了需要重視“OA技術、OA品牌、OA產品、OA服務”四大要素之外,更重要的其實是讓O ...
  • 前言: 在生產環境中,未避免單點故障,每個微服務都會做高可用部署。 通白的說,就是每一個一模一樣的服務會根據需求提供多分在多台機器上。 那麼在大併發的情況下,如何分配服務可以快速得到響應,就成為了我們要解決的問題。 Ribbon就是一款優秀的客戶端負載均衡機制。 什麼是客戶端負載均衡呢? 就是由服務 ...
  • 本文探討如下幾個問題: 什麼是架構屬性 約束和架構屬性的關係 有哪些架構屬性 各個架構屬性涉及知識點 什麼是架構屬性 首先,問個很簡單的問題!請看下麵的Java代碼: 請問上面的代碼中: name和age被稱為Person這個類的什麼? skill又稱為Person這個類的什麼呢? name和age ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...