關於Dapper的介紹,我想很多人都對它有一定的了解,這個類似一個輕型的ORM框架是目前應用非常火的一個東西,據說各方面的性能都不錯,而且可以支持多種數據庫,在開始介紹這個文章之前,我花了不少功夫來學習了Dapper 的相關使用。Dapper.Contrib是對Dapper的進一步封裝,使對象的基本增刪改查等操作進一步簡化,我做了一個案例使用Dapper.Contrib 開發.net core程序,測試它對多種數據庫的處理。
1、Dapper.Contrib的使用
前面介紹過,Dapper.Contrib是對Dapper的進一步封裝,使對象的基本增刪改查等操作進一步簡化。
它主要是通過特性映射的方式實現自定義類和數據庫之間的關系處理,如下是實體類的定義信息。
[Table("T_Customer")] public class CustomerInfo { [ExplicitKey]//非自增長的用此標識 public virtual string ID { get; set; } public virtual string Name { get; set; } public virtual int Age { get; set; } public virtual string Creator { get; set; } public virtual DateTime CreateTime { get; set; } }
Dapper.Contrib的所有實體配置選項
- Table:指定實體對應地數據庫表名,如果類名和數據庫表名不同,需要設置(如案例所示)
- Key:指定此列為自動增長主鍵
- ExplicitKey:指定此列為非自動增長主鍵(例如guid,字符串列)
- Computed:計算屬性,此列不作為更新
- Write:指定列是否可寫
通過定義好實體類和數據庫表的映射關系,就可以通過強類型處理相關的接口了,如下所示。
T Get<T>(id); IEnumerable<T> GetAll<T>(); int Insert<T>(T obj); int Insert<T>(Enumerable<T> list); bool Update<T>(T obj); bool Update<T>(Enumerable<T> list); bool Delete<T>(T obj); bool Delete<T>(Enumerable<T> list); bool DeleteAll<T>();
這樣通過映射指定表名或者字段信息后,就可以知道類和表之間的關系,可以封裝對應的強類型處理接口了。
2、Dapper.Contrib 開發.net core程序
我們創建一個空白的.net core程序框架后,就在它的基礎上做一些Dapper的數據庫測試。
首先為了考慮多數據庫的處理,我們需要創建一個配置文件,並可以動態配置不同的數據庫,配置文件appSettings.json如下所示。
上面我配置了多種數據庫的連接字符串,並且通過動態指定節點名稱和數據庫類型,來實現對項目指向不同數據庫的訪問。
例如我們准備需要讓Dapper支持我們常見的數據庫類型,如下定義數據庫類型。
/// <summary> /// 數據庫類型定義 /// </summary> public enum DatabaseType { SqlServer, //SQLServer數據庫 MySql, //Mysql數據庫 Npgsql, //PostgreSQL數據庫 Oracle, //Oracle數據庫 Sqlite, //SQLite數據庫 DB2 //IBM DB2數據庫 }
對於不同的數據庫信息,我們需要根據不同的配置連接字符串,並創建對應的數據庫連接對象供Dapper使用,如對於SQLServer的數據庫,那么創建的是SqlConnection對象,對於Mysql,創建的是MySqlConnection連接對象,對於PostgreSQL對應的是NpgsqlConnection,以此類推。而Dapper則通過對連接對象的擴展實現了多種數據請求。
對於多數據庫的支持,我們需要統一解析配置內容appSetting.json的內容,並返回不同數據庫的連接對象,如下是連接工廠的統一處理方式,通過 CreateConnection() 返回配置的連接對象。
/// <summary> /// 數據庫連接輔助類 /// </summary> public class ConnectionFactory { /// <summary> /// 轉換數據庫類型 /// </summary> /// <param name="databaseType">數據庫類型</param> /// <returns></returns> private static DatabaseType GetDataBaseType(string databaseType) { DatabaseType returnValue = DatabaseType.SqlServer; foreach (DatabaseType dbType in Enum.GetValues(typeof(DatabaseType))) { if (dbType.ToString().Equals(databaseType, StringComparison.OrdinalIgnoreCase)) { returnValue = dbType; break; } } return returnValue; } /// <summary> /// 獲取數據庫連接 /// </summary> /// <returns></returns> public static IDbConnection CreateConnection() { IDbConnection connection = null; //獲取配置進行轉換 var type = AppConfig.GetConfig("ComponentDbType"); var dbType = GetDataBaseType(type); //DefaultDatabase 根據這個配置項獲取對應連接字符串 var database = AppConfig.GetConfig("DefaultDatabase"); if (string.IsNullOrEmpty(database)) { database = "sqlserver";//默認配置 } var strConn = AppConfig.Configuration.GetConnectionString(database); switch (dbType) { case DatabaseType.SqlServer: connection = new System.Data.SqlClient.SqlConnection(strConn); break; case DatabaseType.MySql: connection = new MySql.Data.MySqlClient.MySqlConnection(strConn); break; case DatabaseType.Npgsql: connection = new Npgsql.NpgsqlConnection(strConn); break; case DatabaseType.Sqlite: connection = new SQLiteConnection(strConn); break; case DatabaseType.Oracle: connection = new Oracle.ManagedDataAccess.Client.OracleConnection(strConn); //connection = new System.Data.OracleClient.OracleConnection(strConn); break; case DatabaseType.DB2: //connection = new System.Data.OleDb.OleDbConnection(strConn); break; } return connection; } }
有了數據庫對象工廠,我們的配置就可以動態化了。
下面我們來看看,獲得這些連接對象后,如何通過Dapper.Contrib來獲取對應的對象了,下面的類是常規的對數據庫信息的處理,包括常規的增刪改查等基礎接口。
/// <summary> /// 常規的數據訪問層 /// </summary> public class Customer { public IDbConnection Connection { get { var connection = ConnectionFactory.CreateConnection(); connection.Open(); return connection; } } public IEnumerable<CustomerInfo> GetAll() { using (IDbConnection dbConnection = Connection) { return dbConnection.GetAll<CustomerInfo>(); //return dbConnection.Query<CustomerInfo>("SELECT * FROM T_Customer"); } } public CustomerInfo FindByID(string id) { using (IDbConnection dbConnection = Connection) { return dbConnection.Get<CustomerInfo>(id); //string query = "SELECT * FROM T_Customer WHERE ID = @Id"; //return dbConnection.Query<CustomerInfo>(query, new { Id = id }).FirstOrDefault(); } } public bool Insert(CustomerInfo info) { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.Insert(info) > 0; result = true; //string query = "INSERT INTO T_Customer (ID, Name, Age, Creator, CreateTime)" // + " VALUES(@ID, @Name, @Age, @Creator, @CreateTime)"; //result = dbConnection.Execute(query, info) > 0; } return result; } public bool Update(CustomerInfo prod) { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.Update(prod); //string query = "UPDATE T_Customer SET Name = @Name," // + " Age = @Age, Creator= @Creator, CreateTime=@CreateTime" // + " WHERE ID = @ID"; //result = dbConnection.Execute(query, prod) > 0; } return result; } public bool Delete(string id) { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.Delete(new CustomerInfo { ID = id }); //string query = "DELETE FROM T_Customer WHERE ID = @Id"; //result = dbConnection.Execute(query, new { ID = id }) > 0; } return result; } public bool DeleteAll() { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.DeleteAll<CustomerInfo>(); //string query = "DELETE FROM T_Customer WHERE ID = @Id"; //result = dbConnection.Execute(query, new { ID = id }) > 0; } return result; } }
其中的備注部分的代碼是等同於上面的執行代碼的,是Dapper 的SQL版本的一種處理方式。
我們看到,對於Customer表來說,使用對象的接口處理,我們已經隔離了很多硬編碼的SQL處理,不過我們還可以對它進行進一步的優化處理。
我們定義一個通用的BaseDAL來剝離常規的增刪改查處理,並且把同步和異步的操作分來兩個文件來管理,同步處理的基類如下代碼所示。
/// <summary> /// 數據庫訪問基類 /// </summary> /// <typeparam name="T">實體類類型</typeparam> public partial class BaseDAL<T> where T : class { /// <summary> /// 對象的表名 /// </summary> public string TableName { get; set; } /// <summary> /// 主鍵屬性對象 /// </summary> public PropertyInfo PrimaryKey { get; set; } public BaseDAL() { this.TableName = EntityHelper.GetTableName(typeof(T)); this.PrimaryKey = EntityHelper.GetSingleKey<T>(); } /// <summary> /// 數據庫連接 /// </summary> protected IDbConnection Connection { get { var connection = ConnectionFactory.CreateConnection(); connection.Open(); return connection; } } /// <summary> /// 返回數據庫所有的對象集合 /// </summary> /// <returns></returns> public IEnumerable<T> GetAll() { using (IDbConnection dbConnection = Connection) { return dbConnection.GetAll<T>(); } } /// <summary> /// 查詢數據庫,返回指定ID的對象 /// </summary> /// <param name="id">主鍵的值</param> /// <returns></returns> public T FindByID(object id) { using (IDbConnection dbConnection = Connection) { return dbConnection.Get<T>(id); } } /// <summary> /// 插入指定對象到數據庫中 /// </summary> /// <param name="info">指定的對象</param> /// <returns></returns> public bool Insert(T info) { bool result = false; using (IDbConnection dbConnection = Connection) { dbConnection.Insert(info); result = true; } return result; } /// <summary> /// 插入指定對象集合到數據庫中 /// </summary> /// <param name="list">指定的對象集合</param> /// <returns></returns> public bool Insert(IEnumerable<T> list) { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.Insert(list) > 0; } return result; } /// <summary> /// 更新對象屬性到數據庫中 /// </summary> /// <param name="info">指定的對象</param> /// <returns></returns> public bool Update(T info) { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.Update(info); } return result; } /// <summary> /// 更新指定對象集合到數據庫中 /// </summary> /// <param name="list">指定的對象集合</param> /// <returns></returns> public bool Update(IEnumerable<T> list) { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.Update(list); } return result; } /// <summary> /// 從數據庫中刪除指定對象 /// </summary> /// <param name="info">指定的對象</param> /// <returns></returns> public bool Delete(T info) { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.Delete(info); } return result; } /// <summary> /// 從數據庫中刪除指定對象集合 /// </summary> /// <param name="list">指定的對象集合</param> /// <returns></returns> public bool Delete(IEnumerable<T> list) { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.Delete(list); } return result; } /// <summary> /// 根據指定對象的ID,從數據庫中刪除指定對象 /// </summary> /// <param name="id">對象的ID</param> /// <returns></returns> public bool Delete(object id) { bool result = false; using (IDbConnection dbConnection = Connection) { string query = string.Format("DELETE FROM {0} WHERE {1} = @id", TableName, PrimaryKey.Name); var parameters = new DynamicParameters(); parameters.Add("@id", id); result = dbConnection.Execute(query, parameters) > 0; } return result; } /// <summary> /// 從數據庫中刪除所有對象 /// </summary> /// <returns></returns> public bool DeleteAll() { bool result = false; using (IDbConnection dbConnection = Connection) { result = dbConnection.DeleteAll<T>(); } return result; } }
異步類的代碼如下所示。
/// <summary> /// 數據庫訪問基類 /// </summary> /// <typeparam name="T">實體類類型</typeparam> public partial class BaseDAL<T> where T : class { /// <summary> /// 返回數據庫所有的對象集合 /// </summary> /// <returns></returns> public virtual async Task<IEnumerable<T>> GetAllAsync() { using (IDbConnection dbConnection = Connection) { return await dbConnection.GetAllAsync<T>(); } } /// <summary> /// 查詢數據庫,返回指定ID的對象 /// </summary> /// <param name="id">主鍵的值</param> /// <returns></returns> public virtual async Task<T> FindByIDAsync(object id) { using (IDbConnection dbConnection = Connection) { return await dbConnection.GetAsync<T>(id); } } /// <summary> /// 插入指定對象到數據庫中 /// </summary> /// <param name="info">指定的對象</param> /// <returns></returns> public virtual async Task<bool> InsertAsync(T info) { bool result = false; using (IDbConnection dbConnection = Connection) { await dbConnection.InsertAsync(info); result = true; } return await Task<bool>.FromResult(result); } /// <summary> /// 插入指定對象集合到數據庫中 /// </summary> /// <param name="list">指定的對象集合</param> /// <returns></returns> public virtual async Task<bool> InsertAsync(IEnumerable<T> list) { using (IDbConnection dbConnection = Connection) { return await dbConnection.InsertAsync(list) > 0; } } /// <summary> /// 更新對象屬性到數據庫中 /// </summary> /// <param name="info">指定的對象</param> /// <returns></returns> public virtual async Task<bool> UpdateAsync(T info) { using (IDbConnection dbConnection = Connection) { return await dbConnection.UpdateAsync(info); } } /// <summary> /// 更新指定對象集合到數據庫中 /// </summary> /// <param name="list">指定的對象集合</param> /// <returns></returns> public virtual async Task<bool> UpdateAsync(IEnumerable<T> list) { using (IDbConnection dbConnection = Connection) { return await dbConnection.UpdateAsync(list); } } /// <summary> /// 從數據庫中刪除指定對象 /// </summary> /// <param name="info">指定的對象</param> /// <returns></returns> public virtual async Task<bool> DeleteAsync(T info) { using (IDbConnection dbConnection = Connection) { return await dbConnection.DeleteAsync(info); } } /// <summary> /// 從數據庫中刪除指定對象集合 /// </summary> /// <param name="list">指定的對象集合</param> /// <returns></returns> public virtual async Task<bool> DeleteAsync(IEnumerable<T> list) { using (IDbConnection dbConnection = Connection) { return await dbConnection.DeleteAsync(list); } } /// <summary> /// 根據指定對象的ID,從數據庫中刪除指定對象 /// </summary> /// <param name="id">對象的ID</param> /// <returns></returns> public virtual async Task<bool> DeleteAsync(object id) { using (IDbConnection dbConnection = Connection) { string query = string.Format("DELETE FROM {0} WHERE {1} = @id", TableName, PrimaryKey.Name); var parameters = new DynamicParameters(); parameters.Add("@id", id); return await dbConnection.ExecuteAsync(query, parameters) > 0; } } /// <summary> /// 從數據庫中刪除所有對象 /// </summary> /// <returns></returns> public virtual async Task<bool> DeleteAllAsync() { using (IDbConnection dbConnection = Connection) { return await dbConnection.DeleteAllAsync<T>(); } } }
這樣,我們如果需要增加一個如客戶信息表的管理類,就很簡單的繼承基類就可以了,代碼很少,但是增刪改查接口一個也少不了。
/// <summary> /// 繼承基類對象管理 /// </summary> public class CustomerDAL :BaseDAL<CustomerInfo> { }
為了測試一下數據訪問層的處理接口,我創建了一個.net core的控制台程序進行測試,如下項目視圖所示。
主要目的是確認數據處理的效果。
我們在Program.cs類里面增加相關的測試代碼,為了簡便和處理效果沒有用UnitTest處理。
//創建管理對象,並測試接口 var customer = new CustomerDAL(); var list = customer.GetAll(); foreach (var item in list) { Console.WriteLine(item.ToJson()); var info = customer.FindByID(item.ID); Console.WriteLine(info.ToJson()); Console.WriteLine(); } //插入記錄 var insertInfo = new CustomerInfo() { Name = "test", Age = 30, Creator = "test" }; var insertList = new List<CustomerInfo>() { insertInfo }; var flag = customer.Insert(insertList); Console.WriteLine("插入操作" + (flag ? "成功" : "失敗")); Console.WriteLine("插入的新內容"); insertInfo = customer.FindByID(insertInfo.ID); Console.WriteLine(insertInfo.ToJson()); Console.WriteLine("更新內容"); insertInfo.Name = "Test" + DateTime.Now.ToShortDateString(); flag = customer.Update(insertInfo); Console.WriteLine("更新操作" + (flag ? "成功" : "失敗")); Console.WriteLine("更新的新內容"); insertInfo = customer.FindByID(insertInfo.ID); Console.WriteLine(insertInfo.ToJson()); Console.WriteLine("刪除內容"); flag = customer.Delete(insertInfo.ID); Console.WriteLine("刪除操作" + (flag ? "成功" : "失敗")); Console.WriteLine("所有內容"); list = customer.GetAll(); foreach (var item in list) { Console.WriteLine(item.ToJson()); Console.WriteLine(); } Console.ReadLine();
測試Mysql、SQLite數據庫同樣沒有問題
Mysql配置信息如下
處理的Mysql記錄信息如下。
SQLite配置信息如下
處理SQLite數據信息如下
而在處理PostgreSQL的信息(配置節點npgsql里面)的時候,查詢的主鍵好像和大小寫有關系,導致插入記錄出錯。
而Oracle我采用的是Oracle.ManagedDataAccess.Core進行訪問,由於我本地Oracle數據庫偵聽處理有點問題,因此沒有測試成功,暫不予置評。
而對於數據庫的支持問題,導致我重新審核一下是否采用Dapper.Contrib還是其他Dapper方式來構建數據庫訪問基類的問題,我需要兼容多種數據庫的信息,並且能夠盡可能的封裝常規的增刪改查等操作,其中目前的基類還沒有加入更加復雜的查詢操作,分頁操作等功能,在解決這些困惑問題,才會繼續考慮把底層支持的接口全部完善。