Asp.Net Core + Dapper + Repository 模式 + TDD 學習筆記

来源:http://www.cnblogs.com/landonzeng/archive/2017/01/04/6248926.html
-Advertisement-
Play Games

0x00 前言 之前一直使用的是 EF ,做了一個簡單的小項目後發現 EF 的表現並不是很好,就比如聯表查詢,因為現在的 EF Core 也沒有啥好用的分析工具,所以也不知道該怎麼寫 Linq 生成出來的 Sql 效率比較高,於是這次的期末大作業決定使用性能強勁、輕便小巧的 ORM —— Dappe ...


0x00 前言

之前一直使用的是 EF ,做了一個簡單的小項目後發現 EF 的表現並不是很好,就比如聯表查詢,因為現在的 EF Core 也沒有啥好用的分析工具,所以也不知道該怎麼寫 Linq 生成出來的 Sql 效率比較高,於是這次的期末大作業決定使用性能強勁、輕便小巧的 ORM —— Dapper。

0x01 Repository 模式

Repository 模式幾乎出現在所有的 asp.net 樣例中,主要的作用是給業務層提供數據訪問的能力,與 DAL 的區別就在於:

Repository模式
Repository 是DDD中的概念,強調 Repository 是受 Domain 驅動的, Repository 中定義的功能要體現 Domain 的意圖和約束,而 Dal 更純粹的就是提供數據訪問的功能,並不嚴格受限於 Business 層。使用 Repository ,隱含著一種意圖傾向,就是 Domain 需要什麼我才提供什麼,不該提供的功能就不要提供,一切都是以 Domain 的需求為核心。
而使用Dal,其意圖傾向在於我 Dal 層能使用的資料庫訪問操作提供給 Business 層,你 Business 要用哪個自己選.換一個 Business 也可以用我這個 Dal,一切是以我 Dal 能提供什麼操作為核心.

0x02 TDD(測試驅動開發)

TDD 的基本思路就是通過測試來推動整個開發的進行。而測試驅動開發技術並不只是單純的測試工作。
在我看來,TDD 的實施可以帶來以下的好處:

  • 在一個介面尚未完全確定的時候,通過編寫測試用例,可以幫助我們更好的描述介面的行為,幫助我們更好的瞭解抽象的需求。
  • 編寫測試用例的過程能夠促使我們將功能分解開,做出“高內聚,低耦合”的設計,因此,TDD 也是我們設計高可復用性的代碼的過程。
  • 編寫測試用例也是對介面調用方法最詳細的描述,Documation is cheap, show me the examples。測試用例代碼比詳盡的文檔不知道高到哪裡去了。
  • 測試用例還能夠儘早的幫助我們發現代碼的錯誤,每當代碼發生了修改,可以方便的幫助我們驗證所做的修改對已經有效的功能是否有影響,從而使我們能夠更快的發現並定位 bug。

0x03 建模

在期末作業的系統中,需要實現一個站內通知的功能,首先,讓我們來簡單的建個模:

然後,依照這個模型,我創建好了對應的實體與介面:

 1 public interface IInsiteMsgService
 2 {
 3     /// <summary>
 4     /// 給一組用戶發送指定的站內消息
 5     /// </summary>
 6     /// <param name="msgs">站內消息數組</param>
 7     Task SentMsgsAsync(IEnumerable<InsiteMsg> msgs);
 8 
 9     /// <summary>
10     /// 發送一條消息給指定的用戶
11     /// </summary>
12     /// <param name="msg">站內消息</param>
13     void SentMsg(InsiteMsg msg);
14 
15     /// <summary>
16     /// 將指定的消息設置為已讀
17     /// </summary>
18     /// <param name="msgIdRecordIds">用戶消息記錄的 Id</param>
19     void ReadMsg(IEnumerable<int> msgIdRecordIds);
20 
21     /// <summary>
22     /// 獲取指定用戶的所有的站內消息,包括已讀與未讀
23     /// </summary>
24     /// <param name="userId">用戶 Id</param>
25     /// <returns></returns>
26     IEnumerable<InsiteMsg> GetInbox(int userId);
27 
28     /// <summary>
29     /// 刪除指定用戶的一些消息記錄
30     /// </summary>
31     /// <param name="userId">用戶 Id</param>
32     /// <param name="insiteMsgIds">用戶消息記錄 Id</param>
33     void DeleteMsgRecord(int userId, IEnumerable<int> insiteMsgIds);
34     }
View Code

InsiteMessage 實體:

 1 public class InsiteMsg
 2 {
 3     public int InsiteMsgId { get; set; }
 4     /// <summary>
 5     /// 消息發送時間
 6     /// </summary>
 7     public DateTime SentTime { get; set; }
 8 
 9     /// <summary>
10     /// 消息閱讀時間,null 說明消息未讀
11     /// </summary>
12     public DateTime? ReadTime { get; set; }
13     
14     public int UserId { get; set; }
15 
16     /// <summary>
17     /// 消息內容
18     /// </summary>
19     [MaxLength(200)]
20     public string Content { get; set; }
21 
22     public bool Status { get; set; }
23 }
View Code

建立測試

接下來,建立測試用例,來描述 Service 每個方法的行為,這裡以 SentMsgsAsync 舉例:

  1. 消息的狀態如果是 false ,則引發 ArgumentException ,且不會被持久化
  2. 消息的內容如果是空的,則引發 ArgumentException ,且不會被持久化

根據上面的約束,測試用例代碼也就出來了

  1 public class InsiteMsgServiceTests
  2 {
  3     /// <summary>
  4     /// 消息發送成功,添加到資料庫
  5     /// </summary>
  6     [Fact]
  7     public void SentMsgTest()
  8     {
  9         //Mock repository
 10         List<InsiteMsg> dataSet = new List<InsiteMsg>();
 11 
 12         var msgRepoMock = new Mock<IInsiteMsgRepository>();
 13         msgRepoMock.Setup(r => r.InsertAsync(It.IsAny<IEnumerable<InsiteMsg>>())).Callback<IEnumerable<InsiteMsg>>((m) =>
 14         {
 15             dataSet.AddRange(m);
 16         });
 17 
 18         //Arrange
 19         IInsiteMsgService msgService = new InsiteMsgService(msgRepoMock.Object);
 20 
 21         var msgs = new List<InsiteMsg>
 22         {
 23             new InsiteMsg { Content="fuck", Status=true, UserId=123 },
 24             new InsiteMsg { Content="fuck", Status=true, UserId=123 },
 25             new InsiteMsg { Content="fuck", Status=true, UserId=123 },
 26             new InsiteMsg { Content="fuck", Status=true, UserId=123 },
 27         };
 28 
 29         //action
 30         msgService.SentMsgsAsync(msgs);
 31 
 32         dataSet.Should().BeEquivalentTo(msgs);
 33     }
 34 
 35     /// <summary>
 36     /// 消息的狀態如果是 false ,則引發 <see cref="ArgumentException"/>,且不會被持久化
 37     /// </summary>
 38     [Fact]
 39     public void SentMsgWithFalseStatusTest()
 40     {
 41         //Mock repository
 42         List<InsiteMsg> dataSet = new List<InsiteMsg>();
 43         var msgRepoMock = new Mock<IInsiteMsgRepository>();
 44         msgRepoMock.Setup(r => r.InsertAsync(It.IsAny<IEnumerable<InsiteMsg>>())).Callback<IEnumerable<InsiteMsg>>((m) =>
 45         {
 46             dataSet.AddRange(m);
 47         });
 48 
 49         IInsiteMsgService msgService = new InsiteMsgService(msgRepoMock.Object);
 50 
 51         List<InsiteMsg> msgs = new List<InsiteMsg>
 52         {
 53             new InsiteMsg { Status = false, Content = "fuck" },
 54             new InsiteMsg { Status = true, Content = "fuck" }
 55         };
 56 
 57         var exception = Record.ExceptionAsync(async () => await msgService.SentMsgsAsync(msgs));
 58         exception?.Result.Should().NotBeNull();
 59         Assert.IsType<ArgumentException>(exception.Result);
 60         dataSet.Count.Should().Equals(0);
 61     }
 62 
 63     /// <summary>
 64     /// 消息的內容如果是空的,則引發 <see cref="ArgumentException"/>,且不會被持久化
 65     /// </summary>
 66     [Fact]
 67     public void SentMsgWithEmptyContentTest()
 68     {
 69         //Mock repository
 70         List<InsiteMsg> dataSet = new List<InsiteMsg>();
 71         var msgRepoMock = new Mock<IInsiteMsgRepository>();
 72         msgRepoMock.Setup(r => r.InsertAsync(It.IsAny<IEnumerable<InsiteMsg>>())).Callback<IEnumerable<InsiteMsg>>((m) =>
 73         {
 74             dataSet.AddRange(m);
 75         });
 76 
 77 
 78         IInsiteMsgService msgService = new InsiteMsgService(msgRepoMock.Object);
 79 
 80         List<InsiteMsg> msgs = new List<InsiteMsg>
 81         {
 82             new InsiteMsg { Status = true, Content = "" }// empty
 83         };
 84 
 85         var exception = Record.ExceptionAsync(async () => await msgService.SentMsgsAsync(msgs));
 86         exception?.Result.Should().NotBeNull(because: "消息內容是空字元串");
 87         Assert.IsType<ArgumentException>(exception.Result);
 88         dataSet.Count.Should().Equals(0);
 89 
 90         msgs = new List<InsiteMsg>
 91         {
 92             new InsiteMsg { Status = true, Content = " " }// space only
 93         };
 94 
 95         exception = Record.ExceptionAsync(async () => await msgService.SentMsgsAsync(msgs));
 96         exception?.Result.Should().NotBeNull(because: "消息內容只包含空格");
 97         Assert.IsType<ArgumentException>(exception.Result);
 98         dataSet.Count.Should().Equals(0);
 99 
100         msgs = new List<InsiteMsg>
101         {
102             new InsiteMsg { Status = true, Content = null }// null
103         };
104 
105         exception = Record.ExceptionAsync(async () => await msgService.SentMsgsAsync(msgs));
106         exception?.Result.Should().NotBeNull(because: "消息內容是 null");
107         Assert.IsType<ArgumentException>(exception.Result);
108         dataSet.Count.Should().Equals(0);
109     }
110 }
View Code

實現介面以通過測試

 1 namespace Hive.Domain.Services.Concretes
 2 {
 3     public class InsiteMsgService : IInsiteMsgService
 4     {
 5         private readonly IInsiteMsgRepository _msgRepo;
 6 
 7         public InsiteMsgService(IInsiteMsgRepository msgRepo)
 8         {
 9             _msgRepo = msgRepo;
10         }
11 
12 
13         public async Task SentMsgsAsync(IEnumerable<InsiteMsg> msgs)
14         {
15             foreach (InsiteMsg msg in msgs)
16             {
17                 if (!msg.Status || string.IsNullOrWhiteSpace(msg.Content))
18                 {
19                     throw new ArgumentException("不能將無效的消息插入", nameof(msgs));
20                 }
21                 msg.SentTime = DateTime.Now;
22                 msg.ReadTime = null;
23             }
24             await _msgRepo.InsertAsync(msgs);
25         }
26 
27         public void SentMsg(InsiteMsg msg)
28         {
29             if (!msg.Status || string.IsNullOrWhiteSpace(msg.Content))
30             {
31                 throw new ArgumentException("不能將無效的消息插入", nameof(msg));
32             }
33             msg.SentTime = DateTime.Now;
34             msg.ReadTime = null;
35             _msgRepo.Insert(msg);
36         }
37 
38         public void ReadMsg(IEnumerable<int> msgs, int userId)
39         {
40             var ids = msgs.Distinct();
41             _msgRepo.UpdateReadTime(ids, userId);
42         }
43 
44         public async Task<IEnumerable<InsiteMsg>> GetInboxAsync(int userId)
45         {
46             return await _msgRepo.GetByUserIdAsync(userId);
47         }
48 
49         public void DeleteMsgRecord(int userId, IEnumerable<int> insiteMsgIds)
50         {
51             _msgRepo.DeleteMsgRecoreds(userId, insiteMsgIds.Distinct());
52         }
53     }
54 }
View Code

上面的一些代碼很明瞭,就懶得逐塊註釋了,函數註釋足矣~

驗證測試

測試當然全部通過啦,這裡就不放圖了

為了將數據訪問與邏輯代碼分離,這裡我使用了 Repository
模式—— IInsiteMsgRepository ,下麵給出這個介面的定義:

 1 namespace Hive.Domain.Repositories.Abstracts
 2 {
 3     public interface IInsiteMsgRepository
 4     {
 5         /// <summary>
 6         /// 插入一條消息
 7         /// </summary>
 8         /// <param name="msg">消息實體</param>
 9         void Insert(InsiteMsg msg);
10 
11         Task InsertAsync(IEnumerable<InsiteMsg> msgs);
12 
13         /// <summary>
14         /// 根據消息 id 獲取消息內容,不包含閱讀狀態
15         /// </summary>
16         /// <param name="id">消息 Id</param>
17         /// <returns></returns>
18         InsiteMsg GetById(int id);
19 
20         /// <summary>
21         /// 更新消息的閱讀時間為當前時間
22         /// </summary>
23         /// <param name="msgIds">消息的 Id</param>
24         /// <param name="userId">用戶 Id</param>
25         void UpdateReadTime(IEnumerable<int> msgIds,int userId);
26 
27         /// <summary>
28         /// 獲取跟指定用戶相關的所有消息
29         /// </summary>
30         /// <param name="id">用戶 id</param>
31         /// <returns></returns>
32         Task<IEnumerable<InsiteMsg>> GetByUserIdAsync(int id);
33 
34         /// <summary>
35         /// 刪除指定的用戶的消息記錄
36         /// </summary>
37         /// <param name="userId">用戶 Id</param>
38         /// <param name="msgRIds">消息 Id</param>
39         void DeleteMsgRecoreds(int userId, IEnumerable<int> msgRIds);
40     }
41 }
View Code

但是在測試階段,我並不想把倉庫實現掉,所以這裡就用上了 Moq.Mock

1 List<InsiteMsg> dataSet = new List<InsiteMsg>();
2         var msgRepoMock = new Mock<IInsiteMsgRepository>();
3         msgRepoMock.Setup(r => r.InsertAsync(It.IsAny<IEnumerable<InsiteMsg>>())).Callback<IEnumerable<InsiteMsg>>((m) =>
4         {
5             dataSet.AddRange(m);
6         });
View Code

上面的代碼模擬了一個 IInsiteMsgRepository 對象,在我們調用這個對象的 InsertAsync 方法的時候,這個對象就把傳入的參數添加到一個集合中去。
模擬出來的對象可以通過 msgMock.Object 訪問。

0x04 實現 Repository

使用事務

在創建併發送新的站內消息到用戶的時候,需要先插入消息本體,然後再把消息跟目標用戶之間在關聯表中建立聯繫,所以我們需要考慮到下麵兩個問題:

  1. 數據的一致性
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 異常處理在程式中也算是比較重要的一部分了,IL異常處理在C#裡面實現會用到一些新的方法 1.BeginExceptionBlock:異常塊代碼開始,相當於try,但是感覺又不太像 2.EndExceptionBlock:異常塊代碼結束,BeginExceptionBlock相當於try,EndExc ...
  • 對象和類 本篇正式進入面向對象的知識點簡述: 何為對象,佛曰:一花一世界,一木一浮生,一草一天堂,一葉一如來,一砂一極樂,一方一凈土,一笑一塵緣,一念一清靜。可見”萬物皆對象”。 對象:包含數據和操作的實體。 面向過程:面向的是完成這件事兒的過程,強調的是完成這件事兒的動作。 舉例:把大象塞進冰箱里 ...
  • 切換數據存儲方式包括以下幾種: 將文本內容存儲在SqlServer、MySQL、MongoDB等資料庫中 將站點配置信息存儲在資料庫中 將後臺用戶信息存儲在資料庫中 將會員信息存儲在資料庫中 將圖片、視頻等媒體資源存儲在網站目錄以外的地方 切換內容資料庫 Kooboo CMS預設文本數據使用XML文 ...
  • 在開發應用的過程中,不可避免的會使用第三方類庫。之前用過一個WinRTXamlToolkit.UWP,現在微軟官方發佈了一個新的開源控制項庫—— UWPCommunityToolkit 項目代碼托管在Github上:https://github.com/Microsoft/UWPCommunityTo ...
  • 1,現象:網站應用程式池停止運行,系統日誌為: 1) 錯誤應用程式名稱: w3wp.exe,版本: 7.5.7601.17514,時間戳: 0x4ce7a5f8錯誤模塊名稱: unknown,版本: 0.0.0.0,時間戳: 0x00000000異常代碼: 0xc00000fd錯誤偏移量: 0x36 ...
  • 1. 查詢所有欄位 生成SQL語句為: 2. 指定欄位查詢 也可以是: 生成SQL語句為: 3. First和FirstOrDefault 生成SQL語句為: 4. Single和SingleOrDefault 生成的SQL語句為: 5.First,FirstOrDefault,Single,Sin ...
  • 1 using Newtonsoft.Json; 2 using System; 3 using System.Collections.Generic; 4 using System.IO; 5 using System.Linq; 6 using System.Text; 7 using Syst ...
  • 微信小程式也已出來有一段時間了,最近寫了幾款微信小程式項目,今天來說說感受。 首先開發一款微信小程式,最主要的就是針對於公司來運營的,因為,在申請appid(微信小程式ID號)時候,需要填寫相關的公司認證信息如,營業執照等 再次就是用一個未曾開通過公眾號的QQ號或微信號來註冊一個微信小程式號。 最後 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...