本篇我將帶著大家一起來對Dapper進行下封裝並實現基本的增刪改查、分頁操作的同步非同步方法的實現(已實現MSSQL,MySql,PgSQL)。同時我們再實現一下倉儲層的代碼生成器,這樣的話,我們只需要結合業務來實現具體的業務部分的代碼就可以了,可以大大減少我們重覆而又繁瑣的增刪改查操作,多留點時間給 ...
本篇我將帶著大家一起來對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或者群里討論!敬請期待吧!