.NET Core實戰專案之CMS 第十二章 開發篇-Dapper封裝GURD及倉儲程式碼生成器實現
本篇我將帶著大家一起來對Dapper進行下封裝並實現基本的增刪改查、分頁操作的同步非同步方法的實現(已實現MSSQL,MySql,PgSQL)。同時我們再實現一下倉儲層的程式碼生成器,這樣的話,我們只需要結合業務來實現具體的業務部分的程式碼就可以了,可以大大減少我們重複而又繁瑣的增刪改查操作,多留點時間給生活充充電(不會偷懶的程式設計師不是一位好爸爸/好老公/好男朋友)。如果您覺得我的實現過程有所不妥的話,您可以在評論區留言,或者加入我們的千人.Net Core實戰專案交流群637326624交流。另外如果您覺得我的文章對您有所幫助的話希望給個推薦以示支援。專案的原始碼我會託管在GayHub上,地址在文章末尾會給出,自認為程式碼寫的很工整,註釋也很全,你應該能看懂!
本文已收錄至《.NET Core實戰專案之CMS 第一章 入門篇-開篇及總體規劃》
作者:依樂祝
寫在前面
將近一週沒有更新,鬼知道我這麼長時間在幹什麼,你可以認為我在憋大招,在想著怎麼給大家分享更多更實用的東西。其實這只是我偷懶的藉口罷了!下面我們一起來對Dapper進行下封裝吧,然後結合Dapper.SimpleCRUD 來實現基本的增刪改查、分頁操作。這部分功能實現完成後,往下我們也就是基於這些基本操作來實現我們的CMS的業務了,如:許可權部分,選單部分,文章部分的功能。接下來我會對這部分快速的實現,可能會很少更新了,因為這些都是基本的CMS的業務操作,沒多少要分享的內容,畢竟每個人的系統業務都不一樣,這部分的業務實現也是千差萬別的。我後期會把成品直接分享給大家!敬請關注吧!
Dapper的封裝
IDbConnection工廠類的封裝
這部分我實現了一個IDbConnection的工廠類,以便你可以很方便的根據資料庫的型別來建立不同的IDbConnection物件,目前已實現對SqlServer,MySQL,PostgreSQL的實現,具體程式碼如下,根據傳入的引數來進行相關的實現。
/// <summary> /// yilezhu /// 2018.12.13 /// 資料庫連線工廠類 /// </summary> public class ConnectionFactory { /// <summary> /// 獲取資料庫連線 /// </summary> /// <param name="dbtype">資料庫型別</param> /// <param name="conStr">資料庫連線字串</param> /// <returns>資料庫連線</returns> public static IDbConnection CreateConnection(string dbtype, string strConn) { if (dbtype.IsNullOrWhiteSpace()) throw new ArgumentNullException("獲取資料庫連線居然不傳資料庫型別,你想上天嗎?"); if (strConn.IsNullOrWhiteSpace()) throw new ArgumentNullException("獲取資料庫連線居然不傳資料庫型別,你想上天嗎?"); var dbType = GetDataBaseType(dbtype); return CreateConnection(dbType,strConn); } /// <summary> /// 獲取資料庫連線 /// </summary> /// <param name="dbType">資料庫型別</param> /// <param name="conStr">資料庫連線字串</param> /// <returns>資料庫連線</returns> public static IDbConnection CreateConnection(DatabaseType dbType, string strConn) { IDbConnection connection = null; if (strConn.IsNullOrWhiteSpace()) throw new ArgumentNullException("獲取資料庫連線居然不傳資料庫型別,你想上天嗎?"); switch (dbType) { case DatabaseType.SqlServer: connection = new SqlConnection(strConn); break; case DatabaseType.MySQL: connection = new MySqlConnection(strConn); break; case DatabaseType.PostgreSQL: connection = new NpgsqlConnection(strConn); break; default: throw new ArgumentNullException($"這是我的錯,還不支援的{dbType.ToString()}資料庫型別"); } if (connection.State == ConnectionState.Closed) { connection.Open(); } return connection; } /// <summary> /// 轉換資料庫型別 /// </summary> /// <param name="dbtype">資料庫型別字串</param> /// <returns>資料庫型別</returns> public static DatabaseType GetDataBaseType(string dbtype) { if (dbtype.IsNullOrWhiteSpace()) throw new ArgumentNullException("獲取資料庫連線居然不傳資料庫型別,你想上天嗎?"); DatabaseType returnValue = DatabaseType.SqlServer; foreach (DatabaseType dbType in Enum.GetValues(typeof(DatabaseType))) { if (dbType.ToString().Equals(dbtype, StringComparison.OrdinalIgnoreCase)) { returnValue = dbType; break; } } return returnValue; } }
那麼,我們怎麼來使用這個工廠類呢?如下給出呼叫的例項。
是不是很簡單,感覺瞬間少了很多程式碼,這段程式碼摘錄自程式碼生成器裡面。有興趣的自己去檢視原始碼吧!
CRUD及分頁泛型方法的實現
nuget安裝Dapper.SimpleCRUD ,什麼你要問我怎麼安裝?乖乖的回去看第二篇文章吧!那裡會教你如何安裝Nuget包?如果那篇文章裡面沒有,那你就好好想想為啥沒有呢?
新建IBaseRepository泛型介面 定義如下的增刪改查方法的同步非同步介面,其中還包含分頁的實現,具體的程式碼如下:
/** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述: *│ 作 者:yilezhu *│ 版 本:1.0 *│ 建立時間:2018/12/16 20:41:22 *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 名稱空間: Czar.Cms.Core.Repository *│ 介面名稱: IBaseRepository *└──────────────────────────────────────────────────────────────┘ */ using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Czar.Cms.Core.Repository { public interface IBaseRepository<T,TKey> : IDisposable where T : class { #region 同步 /// <summary> /// 通過主鍵獲取實體物件 /// </summary> /// <param name="id">主鍵ID</param> /// <returns></returns> T Get(TKey id); /// <summary> /// 獲取所有的資料 /// </summary> /// <returns></returns> IEnumerable<T> GetList(); /// <summary> /// 執行具有條件的查詢,並將結果對映到強型別列表 /// </summary> /// <param name="whereConditions">條件</param> /// <returns></returns> IEnumerable<T> GetList(object whereConditions); /// <summary> /// 帶引數的查詢滿足條件的資料 /// </summary> /// <param name="conditions">條件</param> /// <param name="parameters">引數</param> /// <returns></returns> IEnumerable<T> GetList(string conditions, object parameters = null); /// <summary> /// 使用where子句執行查詢,並將結果對映到具有Paging的強型別List /// </summary> /// <param name="pageNumber">頁碼</param> /// <param name="rowsPerPage">每頁顯示資料</param> /// <param name="conditions">查詢條件</param> /// <param name="orderby">排序</param> /// <param name="parameters">引數</param> /// <returns></returns> IEnumerable<T> GetListPaged(int pageNumber, int rowsPerPage, string conditions, string orderby, object parameters = null); /// <summary> /// 插入一條記錄並返回主鍵值(自增型別返回主鍵值,否則返回null) /// </summary> /// <param name="entity"></param> /// <returns></returns> int? Insert(T entity); /// <summary> /// 更新一條資料並返回影響的行數 /// </summary> /// <param name="entity"></param> /// <returns>影響的行數</returns> int Update(T entity); /// <summary> /// 根據實體主鍵刪除一條資料 /// </summary> /// <param name="id">主鍵</param> /// <returns>影響的行數</returns> int Delete(TKey id); /// <summary> /// 根據實體刪除一條資料 /// </summary> /// <param name="entity">實體</param> /// <returns>返回影響的行數</returns> int Delete(T entity); /// <summary> /// 條件刪除多條記錄 /// </summary> /// <param name="whereConditions">條件</param> /// <param name="transaction">事務</param> /// <param name="commandTimeout">超時時間</param> /// <returns>影響的行數</returns> int DeleteList(object whereConditions, IDbTransaction transaction = null, int? commandTimeout = null); /// <summary> /// 使用where子句刪除多個記錄 /// </summary> /// <param name="conditions">wher子句</param> /// <param name="parameters">引數</param> /// <param name="transaction">事務</param> /// <param name="commandTimeout">超時時間</param> /// <returns>影響的行數</returns> int DeleteList(string conditions, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null); /// <summary> /// 滿足條件的記錄數量 /// </summary> /// <param name="conditions"></param> /// <param name="parameters"></param> /// <returns></returns> int RecordCount(string conditions = "", object parameters = null); #endregion #region 非同步 /// <summary> /// 通過主鍵獲取實體物件 /// </summary> /// <param name="id">主鍵ID</param> /// <returns></returns> Task<T> GetAsync(TKey id); /// <summary> /// 獲取所有的資料 /// </summary> /// <returns></returns> Task<IEnumerable<T>> GetListAsync(); /// <summary> /// 執行具有條件的查詢,並將結果對映到強型別列表 /// </summary> /// <param name="whereConditions">條件</param> /// <returns></returns> Task<IEnumerable<T>> GetListAsync(object whereConditions); /// <summary> /// 帶引數的查詢滿足條件的資料 /// </summary> /// <param name="conditions">條件</param> /// <param name="parameters">引數</param> /// <returns></returns> Task<IEnumerable<T>> GetListAsync(string conditions, object parameters = null); /// <summary> /// 使用where子句執行查詢,並將結果對映到具有Paging的強型別List /// </summary> /// <param name="pageNumber">頁碼</param> /// <param name="rowsPerPage">每頁顯示資料</param> /// <param name="conditions">查詢條件</param> /// <param name="orderby">排序</param> /// <param name="parameters">引數</param> /// <returns></returns> Task<IEnumerable<T>> GetListPagedAsync(int pageNumber, int rowsPerPage, string conditions, string orderby, object parameters = null); /// <summary> /// 插入一條記錄並返回主鍵值 /// </summary> /// <param name="entity"></param> /// <returns></returns> Task<int?> InsertAsync(T entity); /// <summary> /// 更新一條資料並返回影響的行數 /// </summary> /// <param name="entity"></param> /// <returns>影響的行數</returns> Task<int> UpdateAsync(T entity); /// <summary> /// 根據實體主鍵刪除一條資料 /// </summary> /// <param name="id">主鍵</param> /// <returns>影響的行數</returns> Task<int> DeleteAsync(TKey id); /// <summary> /// 根據實體刪除一條資料 /// </summary> /// <param name="entity">實體</param> /// <returns>返回影響的行數</returns> Task<int> DeleteAsync(T entity); /// <summary> /// 條件刪除多條記錄 /// </summary> /// <param name="whereConditions">條件</param> /// <param name="transaction">事務</param> /// <param name="commandTimeout">超時時間</param> /// <returns>影響的行數</returns> Task<int> DeleteListAsync(object whereConditions, IDbTransaction transaction = null, int? commandTimeout = null); /// <summary> /// 使用where子句刪除多個記錄 /// </summary> /// <param name="conditions">wher子句</param> /// <param name="parameters">引數</param> /// <param name="transaction">事務</param> /// <param name="commandTimeout">超時時間</param> /// <returns>影響的行數</returns> Task<int> DeleteListAsync(string conditions, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null); /// <summary> /// 滿足條件的記錄數量 /// </summary> /// <param name="conditions"></param> /// <param name="parameters"></param> /// <returns></returns> Task<int> RecordCountAsync(string conditions = "", object parameters = null); #endregion } }
然後建立一個BaseRepository泛型類來實現上面的介面,其中多了兩個成員,DbOpion以及IDbConnection,猜猜看這兩個東西有什麼用?後面給出答案
/** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述:倉儲類的基類 *│ 作 者:yilezhu *│ 版 本:1.0 *│ 建立時間:2018/12/16 12:03:02 *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 名稱空間: Czar.Cms.Core.Repository *│ 類 名: BaseRepository *└──────────────────────────────────────────────────────────────┘ */ using Czar.Cms.Core.DbHelper; using Czar.Cms.Core.Options; using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using Dapper; namespace Czar.Cms.Core.Repository { public class BaseRepository<T, TKey> : IBaseRepository<T, TKey> where T : class { protected DbOpion _dbOpion; protected IDbConnection _dbConnection; //public BaseRepository(DbOpion dbOpion) //{ // _dbOpion = dbOpion ?? throw new ArgumentNullException(nameof(DbOpion)); // _dbConnection = ConnectionFactory.CreateConnection(_dbOpion.DbType, _dbOpion.ConnectionString); //} #region 同步 public T Get(TKey id) => _dbConnection.Get<T>(id); public IEnumerable<T> GetList() => _dbConnection.GetList<T>(); public IEnumerable<T> GetList(object whereConditions) => _dbConnection.GetList<T>(whereConditions); public IEnumerable<T> GetList(string conditions, object parameters = null) => _dbConnection.GetList<T>(conditions, parameters); public IEnumerable<T> GetListPaged(int pageNumber, int rowsPerPage, string conditions, string orderby, object parameters = null) { return _dbConnection.GetListPaged<T>(pageNumber, rowsPerPage, conditions, orderby, parameters); } public int? Insert(T entity) => _dbConnection.Insert(entity); public int Update(T entity) => _dbConnection.Update(entity); public int Delete(TKey id) => _dbConnection.Delete<T>(id); public int Delete(T entity) => _dbConnection.Delete(entity); public int DeleteList(object whereConditions, IDbTransaction transaction = null, int? commandTimeout = null) { return _dbConnection.DeleteList<T>(whereConditions, transaction, commandTimeout); } public int DeleteList(string conditions, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null) { return _dbConnection.DeleteList<T>(conditions, parameters, transaction, commandTimeout); } public int RecordCount(string conditions = "", object parameters = null) { return _dbConnection.RecordCount<T>(conditions, parameters); } #endregion #region 非同步 public async Task<T> GetAsync(TKey id) { return await _dbConnection.GetAsync<T>(id); } public async Task<IEnumerable<T>> GetListAsync() { return await _dbConnection.GetListAsync<T>(); } public async Task<IEnumerable<T>> GetListAsync(object whereConditions) { return await _dbConnection.GetListAsync<T>(whereConditions); } public async Task<IEnumerable<T>> GetListAsync(string conditions, object parameters = null) { return await _dbConnection.GetListAsync<T>(conditions, parameters); } public async Task<IEnumerable<T>> GetListPagedAsync(int pageNumber, int rowsPerPage, string conditions, string orderby, object parameters = null) { return await _dbConnection.GetListPagedAsync<T>(pageNumber, rowsPerPage, conditions, orderby, parameters); } public async Task<int?> InsertAsync(T entity) { return await _dbConnection.InsertAsync(entity); } public async Task<int> UpdateAsync(T entity) { return await _dbConnection.UpdateAsync(entity); } public async Task<int> DeleteAsync(TKey id) { return await _dbConnection.DeleteAsync(id); } public async Task<int> DeleteAsync(T entity) { return await _dbConnection.DeleteAsync(entity); } public async Task<int> DeleteListAsync(object whereConditions, IDbTransaction transaction = null, int? commandTimeout = null) { return await _dbConnection.DeleteListAsync<T>(whereConditions, transaction, commandTimeout); } public async Task<int> DeleteListAsync(string conditions, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null) { return await DeleteListAsync(conditions, parameters, transaction, commandTimeout); } public async Task<int> RecordCountAsync(string conditions = "", object parameters = null) { return await _dbConnection.RecordCountAsync<T>(conditions, parameters); } #endregion #region IDisposable Support private bool disposedValue = false; // 要檢測冗餘呼叫 protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: 釋放託管狀態(託管物件)。 } // TODO: 釋放未託管的資源(未託管的物件)並在以下內容中替代終結器。 // TODO: 將大型欄位設定為 null。 disposedValue = true; } } // TODO: 僅當以上 Dispose(bool disposing) 擁有用於釋放未託管資源的程式碼時才替代終結器。 // ~BaseRepository() { // // 請勿更改此程式碼。將清理程式碼放入以上 Dispose(bool disposing) 中。 // Dispose(false); // } // 新增此程式碼以正確實現可處置模式。 public void Dispose() { // 請勿更改此程式碼。將清理程式碼放入以上 Dispose(bool disposing) 中。 Dispose(true); // TODO: 如果在以上內容中替代了終結器,則取消註釋以下行。 // GC.SuppressFinalize(this); } #endregion } }
你沒看錯?我在16號就已經寫好了,為什麼這麼晚才寫部落格分享出來呢?因為我懶~~~~~~~
這裡需要注意,需要安裝SimpleCRUD的Nuget包。另外其他的倉儲方法只需要繼承這個介面以及實現就能夠實現基本的增刪改查操作了。這裡你應該會想,既然繼承就能實現,那何不寫一個倉儲的程式碼生成器來進行生成呢?說幹就幹,下面我們就來實現倉儲的程式碼生成器
倉儲層程式碼生成器
上篇生成資料庫實體的程式碼生成器不知道大家看了沒有,這裡我們只需要在根據每個資料庫表生成資料庫實體的實體順帶著生成下倉儲介面以及倉儲程式碼就可以了。有了思路,我們就擼起袖子加油幹吧
先寫一下倉儲介面程式碼生成器的模板,如下所示:
再寫一下倉儲層的程式碼實現,這裡需要注意一下,需要根據注入的IOptionsSnapshot
來生成_dbOpion以及_dbConnection,上面留給大家的思考題答案就在這裡,如下所示: /** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述:{Comment}介面實現 *│ 作 者:{Author} *│ 版 本:1.0 模板程式碼自動生成 *│ 建立時間:{GeneratorTime} *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 名稱空間: {RepositoryNamespace} *│ 類 名: {ModelName}Repository *└──────────────────────────────────────────────────────────────┘ */ using Czar.Cms.Core.DbHelper; using Czar.Cms.Core.Options; using Czar.Cms.Core.Repository; using Czar.Cms.IRepository; using Czar.Cms.Models; using Microsoft.Extensions.Options; using System; namespace {RepositoryNamespace} { public class {ModelName}Repository:BaseRepository<{ModelName},{KeyTypeName}>, I{ModelName}Repository { public {ModelName}Repository(IOptionsSnapshot<DbOpion> options) { _dbOpion =options.Get("CzarCms"); if (_dbOpion == null) { throw new ArgumentNullException(nameof(DbOpion)); } _dbConnection = ConnectionFactory.CreateConnection(_dbOpion.DbType, _dbOpion.ConnectionString); } } }
接著就是程式碼生成器生成IRepository以及生成Repository的程式碼了!這部分程式碼如下圖所示:
測試程式碼
重新執行下程式碼生成器的程式碼,測試的具體程式碼我已經放在GitHub上了,這裡就不貼出來了,直接上生成結果如下圖所示:
如上圖所示:一次性生成了Models以及Repository,IRepository的程式碼,然後到每個資料夾裡面把對應的程式碼拷貝到對應的專案裡面吧。然後我們隨便開啟一下倉儲以及倉儲介面看下生成後的程式碼如下所示:
/** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述:文章分類 *│ 作 者:yilezhu *│ 版 本:1.0 模板程式碼自動生成 *│ 建立時間:2018-12-18 13:28:43 *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 名稱空間: Czar.Cms.IRepository *│ 介面名稱: IArticleCategoryRepository *└──────────────────────────────────────────────────────────────┘ */ using Czar.Cms.Core.Repository; using Czar.Cms.Models; using System; namespace Czar.Cms.IRepository { public interface IArticleCategoryRepository : IBaseRepository<ArticleCategory, Int32> { } }
/** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述:文章分類介面實現 *│ 作 者:yilezhu *│ 版 本:1.0 模板程式碼自動生成 *│ 建立時間:2018-12-18 13:28:43 *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 名稱空間: Czar.Cms.Repository.SqlServer *│ 類 名: ArticleCategoryRepository *└──────────────────────────────────────────────────────────────┘ */ using Czar.Cms.Core.DbHelper; using Czar.Cms.Core.Options; using Czar.Cms.Core.Repository; using Czar.Cms.IRepository; using Czar.Cms.Models; using Microsoft.Extensions.Options; using System; namespace Czar.Cms.Repository.SqlServer { public class ArticleCategoryRepository:BaseRepository<ArticleCategory,Int32>, IArticleCategoryRepository { public ArticleCategoryRepository(IOptionsSnapshot<DbOpion> options) { _dbOpion =options.Get("CzarCms"); if (_dbOpion == null) { throw new ArgumentNullException(nameof(DbOpion)); } _dbConnection = ConnectionFactory.CreateConnection(_dbOpion.DbType, _dbOpion.ConnectionString); } } }
在倉儲層以及倉儲介面層新增對Czar.Cms.Core的引用,當然你也可以通過Nuget包來進行安裝
Install-Package Czar.Cms.Core -Version 0.1.3
最後在測試程式碼中進行測試,這裡以ArticleCategoryRepository為例進行測試:
[Fact] public void TestBaseFactory() { IServiceProvider serviceProvider = BuildServiceForSqlServer(); IArticleCategoryRepository categoryRepository = serviceProvider.GetService<IArticleCategoryRepository>(); var category = new ArticleCategory { Title = "隨筆", ParentId = 0, ClassList = "", ClassLayer = 0, Sort = 0, ImageUrl = "", SeoTitle = "隨筆的SEOTitle", SeoKeywords = "隨筆的SeoKeywords", SeoDescription = "隨筆的SeoDescription", IsDeleted = false, }; var categoryId = categoryRepository.Insert(category); var list = categoryRepository.GetList(); Assert.True(1 == list.Count()); Assert.Equal("隨筆", list.FirstOrDefault().Title); Assert.Equal("SQLServer", DatabaseType.SqlServer.ToString(), ignoreCase: true); categoryRepository.Delete(categoryId.Value); var count = categoryRepository.RecordCount(); Assert.True(0 == count);
測試結果如下所示,都已經測試成功了:
開原地址
這個系列教程的原始碼我會開放在GitHub以及碼雲上,有興趣的朋友可以下載檢視!覺得不錯的歡迎Star
GitHub:https://github.com/yilezhu/Czar.Cms
碼雲:https://gitee.com/yilezhu/Czar.Cms
如果你覺得這個系列對您有所幫助的話,歡迎以各種方式進行贊助,當然給個Star支援下也是可以滴!另外一種最簡單粗暴的方式就是下面這種直接關注我們的公眾號了:
第一時間收到更新推送。
總結
一路走來,已經更新到第十二篇了,到這裡大夥已經可以基於這個Dapper的封裝進行自己的業務系統的開發了!當然接下來我會繼續完成我們既定的CMS系統的業務功能開發,接下來可以用來分享的東西就很少了,所以我更多的是開發然後把程式碼更新到GitHub以及碼雲上,想看最新的程式碼就獲取dev分支的程式碼,有問題的可以提issue或者群裡討論!敬請期待吧!