基于Dapper二次封装了一个易用的ORM工具类:SqlDapperUtil,把日常能用到的各种CRUD都进行了简化封装,让普通程序员只需关注业务即可,因为非常简单,故直接贴源代码,大家若需使用可以直接复制到项目中,该SqlDapperUtil已广泛用于公司项目中。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Dapper; using System.Data; using System.Data.Common; using System.Reflection; using System.IO; using System.Collections.Concurrent; using System.Data.SqlClient; namespace Zuowj.Common { ////// 基于Dapper的数据操作类封装的工具类 /// Author:左文俊 /// Date:2017/12/11 /// public class SqlDapperUtil { private static string dbConnectionStringConfigPath = null; private readonly static ConcurrentDictionarydbConnNamesCacheDic = new ConcurrentDictionary (); private string dbConnectionName = null; private string dbConnectionString = null; private string dbProviderName = null; private IDbConnection dbConnection = null; private bool useDbTransaction = false; private IDbTransaction dbTransaction = null; #region 私有方法 private IDbConnection GetDbConnection() { bool needCreateNew = false; if (dbConnection == null || string.IsNullOrWhiteSpace(dbConnection.ConnectionString)) { needCreateNew = true; } else if (!MemoryCacheUtil.Contains(dbConnectionName)) { needCreateNew = true; } if (needCreateNew) { dbConnectionString = GetDbConnectionString(dbConnectionName, out dbProviderName); var dbProviderFactory = DbProviderFactories.GetFactory(dbProviderName); dbConnection = dbProviderFactory.CreateConnection(); dbConnection.ConnectionString = dbConnectionString; } if (dbConnection.State == ConnectionState.Closed) { dbConnection.Open(); } return dbConnection; } private string GetDbConnectionString(string dbConnName, out string dbProviderName) { //如果指定的连接字符串配置文件路径,则创建缓存依赖,一旦配置文件更改就失效,再重新读取 string[] connInfos = MemoryCacheUtil.GetOrAddCacheItem(dbConnName, () => { var connStrSettings = ConfigUtil.GetConnectionStringForConfigPath(dbConnName, SqlDapperUtil.DbConnectionStringConfigPath); string dbProdName = connStrSettings.ProviderName; string dbConnStr = connStrSettings.ConnectionString; //LogUtil.Info(string.Format("SqlDapperUtil.GetDbConnectionString>读取连接字符串配置节点[{0}]:{1},ProviderName:{2}", dbConnName, dbConnStr, dbProdName), "SqlDapperUtil.GetDbConnectionString"); return new[] { EncryptUtil.Decrypt(dbConnStr), dbProdName }; }, SqlDapperUtil.DbConnectionStringConfigPath); dbProviderName = connInfos[1]; return connInfos[0]; } private T UseDbConnection (Func queryOrExecSqlFunc) { IDbConnection dbConn = null; try { Type modelType = typeof(T); var typeMap = Dapper.SqlMapper.GetTypeMap(modelType); if (typeMap == null || !(typeMap is ColumnAttributeTypeMapper )) { Dapper.SqlMapper.SetTypeMap(modelType, new ColumnAttributeTypeMapper ()); } dbConn = GetDbConnection(); if (useDbTransaction && dbTransaction == null) { dbTransaction = GetDbTransaction(); } return queryOrExecSqlFunc(dbConn); } catch { throw; } finally { if (dbTransaction == null && dbConn != null) { CloseDbConnection(dbConn); } } } private void CloseDbConnection(IDbConnection dbConn, bool disposed = false) { if (dbConn != null) { if (disposed && dbTransaction != null) { dbTransaction.Rollback(); dbTransaction.Dispose(); dbTransaction = null; } if (dbConn.State != ConnectionState.Closed) { dbConn.Close(); } dbConn.Dispose(); dbConn = null; } } /// /// 获取一个事务对象(如果需要确保多条执行语句的一致性,必需使用事务) /// /// ///private IDbTransaction GetDbTransaction(IsolationLevel il = IsolationLevel.Unspecified) { return GetDbConnection().BeginTransaction(il); } private DynamicParameters ToDynamicParameters(Dictionary paramDic) { return new DynamicParameters(paramDic); } #endregion public static string DbConnectionStringConfigPath { get { if (string.IsNullOrEmpty(dbConnectionStringConfigPath))//如果没有指定配置文件,则取默认的配置文件路径作为缓存依赖路径 { dbConnectionStringConfigPath = BaseUtil.GetConfigPath(); } return dbConnectionStringConfigPath; } set { if (!string.IsNullOrWhiteSpace(value) && !File.Exists(value)) { throw new FileNotFoundException("指定的DB连接字符串配置文件不存在:" + value); } //如果配置文件改变,则可能导致连接字符串改变,故必需清除所有连接字符串的缓存以便后续重新加载字符串 if (!string.Equals(dbConnectionStringConfigPath, value, StringComparison.OrdinalIgnoreCase)) { foreach (var item in dbConnNamesCacheDic) { MemoryCacheUtil.RemoveCacheItem(item.Key); } } dbConnectionStringConfigPath = value; } } public SqlDapperUtil(string connName) { dbConnectionName = connName; if (!dbConnNamesCacheDic.ContainsKey(connName)) //如果静态缓存中没有,则加入到静态缓存中 { dbConnNamesCacheDic[connName] = true; } } /// /// 使用事务 /// public void UseDbTransaction() { useDbTransaction = true; } ////// 获取一个值,param可以是SQL参数也可以是匿名对象 /// ////// /// /// /// /// /// public T GetValue (string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null) { return UseDbConnection((dbConn) => { return dbConn.ExecuteScalar (sql, param, dbTransaction, commandTimeout, commandType); }); } /// /// 获取第一行的所有值,param可以是SQL参数也可以是匿名对象 /// /// /// /// /// /// ///public Dictionary GetFirstValues(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null) { return UseDbConnection((dbConn) => { Dictionary firstValues = new Dictionary (); List indexColNameMappings = new List (); int rowIndex = 0; using (var reader = dbConn.ExecuteReader(sql, param, dbTransaction, commandTimeout, commandType)) { while (reader.Read()) { if ((++rowIndex) > 1) break; if (indexColNameMappings.Count == 0) { for (int i = 0; i < reader.FieldCount; i++) { indexColNameMappings.Add(reader.GetName(i)); } } for (int i = 0; i < reader.FieldCount; i++) { firstValues[indexColNameMappings[i]] = reader.GetValue(i); } } reader.Close(); } return firstValues; }); } /// /// 获取一个数据模型实体类,param可以是SQL参数也可以是匿名对象 /// ////// /// /// /// /// /// public T GetModel (string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null) where T : class { return UseDbConnection((dbConn) => { return dbConn.QueryFirstOrDefault (sql, param, dbTransaction, commandTimeout, commandType); }); } /// /// 获取符合条件的所有数据模型实体类列表,param可以是SQL参数也可以是匿名对象 /// ////// /// /// /// /// /// /// public List GetModelList (string sql, object param = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) where T : class { return UseDbConnection((dbConn) => { return dbConn.Query (sql, param, dbTransaction, buffered, commandTimeout, commandType).ToList(); }); } /// /// 获取符合条件的所有数据并根据动态构建Model类委托来创建合适的返回结果(适用于临时性结果且无对应的模型实体类的情况) /// ////// /// /// /// /// /// /// public T GetDynamicModel (Func , T> buildModelFunc, string sql, object param = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { var dynamicResult = UseDbConnection((dbConn) => { return dbConn.Query(sql, param, dbTransaction, buffered, commandTimeout, commandType); }); return buildModelFunc(dynamicResult); } /// /// 获取符合条件的所有指定返回结果对象的列表(复合对象【如:1对多,1对1】),param可以是SQL参数也可以是匿名对象 /// ////// /// /// /// /// /// /// /// /// /// public List GetMultModelList (string sql, Type[] types, Func
ColumnAttributeTypeMapper辅助类相关代码如下:(如果不考虑实体类的属性与表字段不一致的情况,如下映射类可以不需要添加,同时SqlDapperUtil中移除相关依赖ColumnAttributeTypeMapper逻辑即可)
using Dapper; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace KYExpress.Common { public class ColumnAttributeTypeMapper: FallbackTypeMapper { public ColumnAttributeTypeMapper() : base(new SqlMapper.ITypeMap[] { new CustomPropertyTypeMap( typeof(T), (type, columnName) => type.GetProperties().FirstOrDefault(prop => prop.GetCustomAttributes(false) .OfType () .Any(attr => attr.Name == columnName) ) ), new DefaultTypeMap(typeof(T)) }) { } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class ColumnAttribute : Attribute { public string Name { get; set; } } public class FallbackTypeMapper : SqlMapper.ITypeMap { private readonly IEnumerable _mappers; public FallbackTypeMapper(IEnumerable mappers) { _mappers = mappers; } public ConstructorInfo FindConstructor(string[] names, Type[] types) { foreach (var mapper in _mappers) { try { ConstructorInfo result = mapper.FindConstructor(names, types); if (result != null) { return result; } } catch (NotImplementedException) { } } return null; } public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) { foreach (var mapper in _mappers) { try { var result = mapper.GetConstructorParameter(constructor, columnName); if (result != null) { return result; } } catch (NotImplementedException) { } } return null; } public SqlMapper.IMemberMap GetMember(string columnName) { foreach (var mapper in _mappers) { try { var result = mapper.GetMember(columnName); if (result != null) { return result; } } catch (NotImplementedException) { } } return null; } public ConstructorInfo FindExplicitConstructor() { return _mappers .Select(mapper => mapper.FindExplicitConstructor()) .FirstOrDefault(result => result != null); } } }
使用示例方法如下:
1.先来模拟各种查询数据(由于是直接写模拟SQL输出,故没有条件,也便于大家COPY后直接可以测试结果)
//实例化SqlDapperUtil对象,构造函数是config文件中的connectionStrings的Name名 var dapper = new SqlDapperUtil("LmsConnectionString"); //查询1个值 DateTime nowTime = dapper.GetValue("select getdate() as nowtime"); //查询1行值,并转换成字典(这对于临时查询多个字段而无需定义实体类有用) Dictionary rowValues = dapper.GetFirstValues("select 0 as col0,1 as col1,2 as col2"); //查询1行并返回实体类 Person person = dapper.GetModel ("select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr"); //查询1行表字段与实体类属性不一致映射 Person person2 = dapper.GetModel ("select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddress"); //查询多行返回实体集合 var persons = dapper.GetModelList (@"select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr union all select '李四' as Name,25 as Age,'2018-10-1' as BirthDay,'中国广东深圳' as HomeAddress union all select '王五' as Name,35 as Age,'1982-10-1' as BirthDay,'中国广东广州' as HomeAddress "); //查询多行返回1对1关联实体结果集 var personWithCarResult = dapper.GetMultModelList (@"select t1.*,t2.* from (select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr union all select '李四' as Name,25 as Age,'2018-10-1' as BirthDay,'中国广东深圳' as HomeAddress union all select '王五' as Name,35 as Age,'1982-10-1' as BirthDay,'中国广东广州' as HomeAddress)as t1 inner join ( select '张三' as DriverName,'大众' as Brand,'2018-8-8' as ManufactureDate union all select '李四' as DriverName,'奔驰' as Brand,'2018-1-8' as ManufactureDate union all select '王五' as DriverName,'奥迪' as Brand,'2017-8-8' as ManufactureDate )as t2 on t1.Name=t2.DriverName ", new[] { typeof(Person), typeof(CarInfo) }, (objs) => { Person personItem = objs[0] as Person; CarInfo carItem = objs[1] as CarInfo; personItem.Car = carItem; return personItem; }, splitOn: "DriverName"); //查询多行返回1对多关联实体结果=personWithManyCars List personWithManyCars = new List (); dapper.GetMultModelList (@"select t1.*,t2.* from (select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr union all select '李四' as Name,25 as Age,'2018-10-1' as BirthDay,'中国广东深圳' as HomeAddress union all select '王五' as Name,35 as Age,'1982-10-1' as BirthDay,'中国广东广州' as HomeAddress)as t1 inner join ( select '张三' as DriverName,'大众' as Brand,'2018-8-8' as ManufactureDate union all select '张三' as DriverName,'奔驰' as Brand,'2018-1-8' as ManufactureDate union all select '张三' as DriverName,'奥迪' as Brand,'2017-8-8' as ManufactureDate )as t2 on t1.Name=t2.DriverName ", new[] { typeof(Person), typeof(CarInfo) }, (objs) => { Person personItem = objs[0] as Person; CarInfo carItem = objs[1] as CarInfo; Person personItemMain = personWithManyCars.FirstOrDefault(p => p.Name == personItem.Name); if (personItemMain == null) { personItem.Cars = new List (); personItemMain = personItem; personWithManyCars.Add(personItemMain); } personItemMain.Cars.Add(carItem); return personItemMain; }, splitOn: "DriverName");
2.下面是演示如何进行增、删、改以及动态查询的情况:
//使用事务创建多张表,多条SQL语句写在一起 try { dapper.UseDbTransaction(); dapper.ExecuteCommand(@"create table T_Person(Name nvarchar(20) primary key,Age int,BirthDay datetime,HomeAddress nvarchar(200)); create table T_CarInfo(DriverName nvarchar(20) primary key,Brand nvarchar(50),ManufactureDate datetime)"); dapper.Commit(); } catch (Exception ex) { dapper.Rollback(); //记日志 } //使用事务批量插入多张表的多个记录,多条SQL分多次执行(参数支持批量集合对象传入,无需循环) try { dapper.UseDbTransaction(); dapper.ExecuteCommand(@"insert into T_Person select N'张三' as Name,22 as Age,'2018-1-1' as BirthDay,N'中国广东深圳' as HomeAddress union all select N'李四' as Name,25 as Age,'2018-10-1' as BirthDay,N'中国广东深圳' as HomeAddress union all select N'王五' as Name,35 as Age,'1982-10-1' as BirthDay,N'中国广东广州' as HomeAddress"); var carInfos = dapper.GetModelList(@" select N'张三' as DriverName,N'大众' as Brand,'2018-8-8' as ManufactureDate union all select N'李四' as DriverName,N'奔驰' as Brand,'2018-1-8' as ManufactureDate union all select N'王五' as DriverName,N'奥迪' as Brand,'2017-8-8' as ManufactureDate"); dapper.ExecuteCommand(@"insert into T_CarInfo(DriverName,Brand,ManufactureDate) Values(@DriverName,@Brand,@ManufactureDate)", carInfos); dapper.Commit(); } catch (Exception ex) { dapper.Rollback(); //记日志 } //执行删除,有参数,参数可以是实体类、匿名对象、字典(如有需要,可以是集合,以支持批量操作) bool deleteResult = dapper.ExecuteCommand("delete from T_CarInfo where DriverName=@DriverName", new { DriverName = "李四" }); //构建动态执行SQL语句(以下是更新,查询类似) StringBuilder updateSqlBuilder = new StringBuilder(); var updateParams = new Dictionary (); if (1 == 1) { updateSqlBuilder.Append(",Age=@Age"); updateParams["Age"] = 20; } if (2 == 2) { updateSqlBuilder.Append(",BirthDay=@BirthDay"); updateParams["BirthDay"] = Convert.ToDateTime("2010-1-1"); } if (3 == 3) { updateSqlBuilder.Append(",HomeAddress=@HomeAddress"); updateParams["HomeAddress"] = "中国北京天安门"; } string updateSql = string.Concat("update T_Person set ", updateSqlBuilder.ToString().TrimStart(','), " where Name=@Name"); updateParams["Name"] = "张三"; bool updateResult = dapper.ExecuteCommand(updateSql, updateParams); //查询返回动态自定义结果,之所以不直接返回Dynamic就好,是因为可读性差,故尽可能的在执行后就转成指定的类型 Tuple hasCarInfo = dapper.GetDynamicModel >((rs) => { var result = rs.First(); return Tuple.Create (result.Name, result.CarCount); }, @"select a.Name,count(b.DriverName) as CarCount from T_Person a left join T_CarInfo b on a.Name=b.DriverName where a.Name=@Name group by a.Name", new { Name = "张三" });
3.还有两个方法:BatchCopyData、BatchMoveData,这是特殊封装的,不是基于Dapper而是基于原生的Ado.net及BCP,目的是快速大量跨DB跨表COPY数据或转移数据,使用也不复杂,建议想了解的网友可以查看我以往的文章
以上示例方法用到了两个类,如下:
class Person { public string Name { get; set; } public int Age { get; set; } public DateTime BirthDay { get; set; } [Column(Name = "HomeAddress")] public string HomeAddr { get; set; } public CarInfo Car { get; set; } public ListCars { get; set; } } class CarInfo { public string Brand { get; set; } public DateTime ManufactureDate { get; set; } public string DriverName { get; set; } }
SqlDapperUtil类中依赖了之前我封装的类:如:MemoryCacheUtil(本地内存依赖缓存实用工具类)、ConfigUtil(配置文件管理工具类)、EncryptUtil(加密工具类),如果项目中不想引用这些类,可以移除或改成其它方法即可。
另外说明一下,为了防止和减少因DB连接未及时释放导致的连接池不足等原因,故默认执行所有的CRUD方法都是用完即释放,但有一种情况不会释放就是使用了事务,若使用事务,则必需配套使用:UseDbTransaction、Commit、或失败执行Rollback,否则可能导致未能及时释放对象,当然最终当SqlDapperUtil实例被回收后事务若没有提交或回滚,会强制执行回滚操作并释放事务及连接对象,防止可能的资源浪费情况。
本来早就想总结一下这篇文章,但一直由于工作太忙没有时间,今天利用加班研究.NET CORE的空隙时间完成,请大家支持,有好东西我一定会分享的,虽然不一定高大上,但一定实用且项目中有实战过的。