細說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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...