基于Dapper二次封装了一个易用的ORM工具类:SqlDapperUtil

图片 2

基于Dapper二次封装了一个易用的ORM工具类:SqlDapperUtil,把日常能用到的各种CRUD都进行了简化封装,让普通程序员只需关注业务即可,因为非常简单,故直接贴源代码,大家若需使用可以直接复制到项目中,该SqlDapperUtil已广泛用于公司项目中。

现在成熟的ORM比比皆是,这里只介绍Dapper的使用(最起码我在使用它,已经运用到项目中,小伙伴们反馈还可以)。

   
最近趁着不忙,在构思一个搭建一个开源的完整项目,至于原因以及整个项目框架后边文章我再说明。既然要起一个完整的项目,那么数据仓储访问就必不可少,这篇文章我主要介绍这个新项目(OSS.Core)中我对仓储层的简单思考和实现过程(当前项目还处在搭建阶段),主要集中在以下几个方面:

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
{
    /// <summary>
    /// 基于Dapper的数据操作类封装的工具类
    /// Author:左文俊
    /// Date:2017/12/11
    /// </summary>
    public class SqlDapperUtil
    {
        private static string dbConnectionStringConfigPath = null;
        private readonly static ConcurrentDictionary<string, bool> dbConnNamesCacheDic = new ConcurrentDictionary<string, bool>();

        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<T>(Func<IDbConnection, T> queryOrExecSqlFunc)
        {
            IDbConnection dbConn = null;

            try
            {
                Type modelType = typeof(T);
                var typeMap = Dapper.SqlMapper.GetTypeMap(modelType);
                if (typeMap == null || !(typeMap is ColumnAttributeTypeMapper<T>))
                {
                    Dapper.SqlMapper.SetTypeMap(modelType, new ColumnAttributeTypeMapper<T>());
                }

                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;
            }
        }

        /// <summary>
        /// 获取一个事务对象(如果需要确保多条执行语句的一致性,必需使用事务)
        /// </summary>
        /// <param name="il"></param>
        /// <returns></returns>
        private IDbTransaction GetDbTransaction(IsolationLevel il = IsolationLevel.Unspecified)
        {
            return GetDbConnection().BeginTransaction(il);
        }

        private DynamicParameters ToDynamicParameters(Dictionary<string, object> 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;
            }

        }


        /// <summary>
        /// 使用事务
        /// </summary>
        public void UseDbTransaction()
        {
            useDbTransaction = true;
        }


        /// <summary>
        /// 获取一个值,param可以是SQL参数也可以是匿名对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sql"></param>
        /// <param name="param"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <param name="commandType"></param>
        /// <returns></returns>
        public T GetValue<T>(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
        {
            return UseDbConnection((dbConn) =>
             {
                 return dbConn.ExecuteScalar<T>(sql, param, dbTransaction, commandTimeout, commandType);
             });
        }

        /// <summary>
        /// 获取第一行的所有值,param可以是SQL参数也可以是匿名对象
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="param"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <param name="commandType"></param>
        /// <returns></returns>
        public Dictionary<string, dynamic> GetFirstValues(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
        {
            return UseDbConnection((dbConn) =>
            {
                Dictionary<string, dynamic> firstValues = new Dictionary<string, dynamic>();
                List<string> indexColNameMappings = new List<string>();
                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;

            });
        }

        /// <summary>
        /// 获取一个数据模型实体类,param可以是SQL参数也可以是匿名对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sql"></param>
        /// <param name="param"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <param name="commandType"></param>
        /// <returns></returns>
        public T GetModel<T>(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null) where T : class
        {
            return UseDbConnection((dbConn) =>
            {
                return dbConn.QueryFirstOrDefault<T>(sql, param, dbTransaction, commandTimeout, commandType);
            });
        }

        /// <summary>
        /// 获取符合条件的所有数据模型实体类列表,param可以是SQL参数也可以是匿名对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sql"></param>
        /// <param name="param"></param>
        /// <param name="transaction"></param>
        /// <param name="buffered"></param>
        /// <param name="commandTimeout"></param>
        /// <param name="commandType"></param>
        /// <returns></returns>
        public List<T> GetModelList<T>(string sql, object param = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) where T : class
        {
            return UseDbConnection((dbConn) =>
            {
                return dbConn.Query<T>(sql, param, dbTransaction, buffered, commandTimeout, commandType).ToList();
            });
        }

        /// <summary>
        /// 获取符合条件的所有数据并根据动态构建Model类委托来创建合适的返回结果(适用于临时性结果且无对应的模型实体类的情况)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="buildModelFunc"></param>
        /// <param name="sql"></param>
        /// <param name="param"></param>
        /// <param name="buffered"></param>
        /// <param name="commandTimeout"></param>
        /// <param name="commandType"></param>
        /// <returns></returns>
        public T GetDynamicModel<T>(Func<IEnumerable<dynamic>, 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);
        }

        /// <summary>
        /// 获取符合条件的所有指定返回结果对象的列表(复合对象【如:1对多,1对1】),param可以是SQL参数也可以是匿名对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sql"></param>
        /// <param name="types"></param>
        /// <param name="map"></param>
        /// <param name="param"></param>
        /// <param name="transaction"></param>
        /// <param name="buffered"></param>
        /// <param name="splitOn"></param>
        /// <param name="commandTimeout"></param>
        /// <param name="commandType"></param>
        /// <returns></returns>

        public List<T> GetMultModelList<T>(string sql, Type[] types, Func<object[], T> map, object param = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
        {
            return UseDbConnection((dbConn) =>
            {
                return dbConn.Query<T>(sql, types, map, param, dbTransaction, buffered, splitOn, commandTimeout, commandType).ToList();
            });
        }




        /// <summary>
        /// 执行SQL命令(CRUD),param可以是SQL参数也可以是要添加的实体类
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="param"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <param name="commandType"></param>
        /// <returns></returns>
        public bool ExecuteCommand(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
        {
            return UseDbConnection((dbConn) =>
            {
                int result = dbConn.Execute(sql, param, dbTransaction, commandTimeout, commandType);
                return (result > 0);
            });
        }

        /// <summary>
        /// 批量转移数据(利用SqlBulkCopy实现快速大批量插入到指定的目的表及SqlDataAdapter的批量删除)
        /// </summary>
        public bool BatchMoveData(string srcSelectSql, string srcTableName, List<SqlParameter> srcPrimarykeyParams, string destConnName, string destTableName)
        {

            using (SqlDataAdapter srcSqlDataAdapter = new SqlDataAdapter(srcSelectSql, GetDbConnectionString(dbConnectionName, out dbProviderName)))
            {
                DataTable srcTable = new DataTable();
                SqlCommand deleteCommand = null;
                try
                {
                    srcSqlDataAdapter.AcceptChangesDuringFill = true;
                    srcSqlDataAdapter.AcceptChangesDuringUpdate = false;
                    srcSqlDataAdapter.Fill(srcTable);

                    if (srcTable == null || srcTable.Rows.Count <= 0) return true;

                    string notExistsDestSqlWhere = null;
                    string deleteSrcSqlWhere = null;

                    for (int i = 0; i < srcPrimarykeyParams.Count; i++)
                    {
                        string keyColName = srcPrimarykeyParams[i].ParameterName.Replace("@", "");
                        notExistsDestSqlWhere += string.Format(" AND told.{0}=tnew.{0}", keyColName);
                        deleteSrcSqlWhere += string.Format(" AND {0}=@{0}", keyColName);
                    }

                    string dbProviderName2 = null;
                    using (var destConn = new SqlConnection(GetDbConnectionString(destConnName, out dbProviderName2)))
                    {
                        destConn.Open();

                        string tempDestTableName = "#temp_" + destTableName;
                        destConn.Execute(string.Format("select top 0 * into {0} from {1}", tempDestTableName, destTableName));
                        string destInsertCols = null;
                        using (var destSqlBulkCopy = new SqlBulkCopy(destConn))
                        {
                            try
                            {
                                destSqlBulkCopy.BulkCopyTimeout = 120;
                                destSqlBulkCopy.DestinationTableName = tempDestTableName;
                                foreach (DataColumn col in srcTable.Columns)
                                {
                                    destSqlBulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
                                    destInsertCols += "," + col.ColumnName;
                                }

                                destSqlBulkCopy.BatchSize = 1000;
                                destSqlBulkCopy.WriteToServer(srcTable);
                            }
                            catch (Exception ex)
                            {
                                //LogUtil.Error("SqlDapperUtil.BatchMoveData.SqlBulkCopy:" + ex.ToString(), "SqlDapperUtil.BatchMoveData");
                            }

                            destInsertCols = destInsertCols.Substring(1);

                            destConn.Execute(string.Format("insert into {1}({0}) select {0} from {2} tnew where not exists(select 1 from {1} told where {3})",
                                             destInsertCols, destTableName, tempDestTableName, notExistsDestSqlWhere.Trim().Substring(3)), null, null, 100);
                        }
                        destConn.Close();
                    }

                    deleteCommand = new SqlCommand(string.Format("DELETE FROM {0} WHERE {1}", srcTableName, deleteSrcSqlWhere.Trim().Substring(3)), srcSqlDataAdapter.SelectCommand.Connection);
                    deleteCommand.Parameters.AddRange(srcPrimarykeyParams.ToArray());
                    deleteCommand.UpdatedRowSource = UpdateRowSource.None;
                    deleteCommand.CommandTimeout = 200;

                    srcSqlDataAdapter.DeleteCommand = deleteCommand;
                    foreach (DataRow row in srcTable.Rows)
                    {
                        row.Delete();
                    }

                    srcSqlDataAdapter.UpdateBatchSize = 1000;
                    srcSqlDataAdapter.Update(srcTable);
                    srcTable.AcceptChanges();

                    return true;
                }
                catch (Exception ex)
                {
                    //LogUtil.Error("SqlDapperUtil.BatchMoveData:" + ex.ToString(), "SqlDapperUtil.BatchMoveData");
                    return false;
                }
                finally
                {
                    if (deleteCommand != null)
                    {
                        deleteCommand.Parameters.Clear();
                    }
                }
            }

        }

        /// <summary>
        /// 批量复制数据(把源DB中根据SQL语句查出的结果批量COPY插入到目的DB的目的表中)
        /// </summary>
        public TResult BatchCopyData<TResult>(string srcSelectSql, string destConnName, string destTableName, IDictionary<string, string> colMappings, Func<IDbConnection, TResult> afterCoppyFunc)
        {

            using (SqlDataAdapter srcSqlDataAdapter = new SqlDataAdapter(srcSelectSql, GetDbConnectionString(dbConnectionName, out dbProviderName)))
            {
                DataTable srcTable = new DataTable();
                TResult copyResult = default(TResult);
                try
                {
                    srcSqlDataAdapter.AcceptChangesDuringFill = true;
                    srcSqlDataAdapter.AcceptChangesDuringUpdate = false;
                    srcSqlDataAdapter.Fill(srcTable);

                    if (srcTable == null || srcTable.Rows.Count <= 0) return copyResult;


                    string dbProviderName2 = null;
                    using (var destConn = new SqlConnection(GetDbConnectionString(destConnName, out dbProviderName2)))
                    {
                        destConn.Open();
                        string tempDestTableName = "#temp_" + destTableName;
                        destConn.Execute(string.Format("select top 0 * into {0} from {1}", tempDestTableName, destTableName));
                        bool bcpResult = false;
                        using (var destSqlBulkCopy = new SqlBulkCopy(destConn))
                        {
                            try
                            {
                                destSqlBulkCopy.BulkCopyTimeout = 120;
                                destSqlBulkCopy.DestinationTableName = tempDestTableName;
                                foreach (var col in colMappings)
                                {
                                    destSqlBulkCopy.ColumnMappings.Add(col.Key, col.Value);
                                }

                                destSqlBulkCopy.BatchSize = 1000;
                                destSqlBulkCopy.WriteToServer(srcTable);
                                bcpResult = true;
                            }
                            catch (Exception ex)
                            {
                                //LogUtil.Error("SqlDapperUtil.BatchMoveData.SqlBulkCopy:" + ex.ToString(), "SqlDapperUtil.BatchMoveData");
                            }
                        }

                        if (bcpResult)
                        {
                            copyResult = afterCoppyFunc(destConn);
                        }

                        destConn.Close();
                    }

                    return copyResult;
                }
                catch (Exception ex)
                {
                    //LogUtil.Error("SqlDapperUtil.BatchCopyData:" + ex.ToString(), "SqlDapperUtil.BatchCopyData");
                    return copyResult;
                }
            }

        }


        /// <summary>
        /// 当使用了事务,则最后需要调用该方法以提交所有操作
        /// </summary>
        /// <param name="dbTransaction"></param>
        public void Commit()
        {
            try
            {
                if (dbTransaction.Connection != null && dbTransaction.Connection.State != ConnectionState.Closed)
                {
                    dbTransaction.Commit();
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                if (dbTransaction.Connection != null)
                {
                    CloseDbConnection(dbTransaction.Connection);
                }
                dbTransaction.Dispose();
                dbTransaction = null;
                useDbTransaction = false;

                if (dbConnection != null)
                {
                    CloseDbConnection(dbConnection);
                }
            }
        }

        /// <summary>
        /// 当使用了事务,如果报错或需要中断执行,则需要调用该方法执行回滚操作
        /// </summary>
        /// <param name="dbTransaction"></param>
        public void Rollback()
        {
            try
            {
                if (dbTransaction.Connection != null && dbTransaction.Connection.State != ConnectionState.Closed)
                {
                    dbTransaction.Rollback();
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                if (dbTransaction.Connection != null)
                {
                    CloseDbConnection(dbTransaction.Connection);
                }

                dbTransaction.Dispose();
                dbTransaction = null;
                useDbTransaction = false;
            }
        }

        ~SqlDapperUtil()
        {
            try
            {
                CloseDbConnection(dbConnection, true);
            }
            catch
            { }
        }

    }
}

优点:

  1. 数据仓储层的需求

  2. ORM框架选择

  3. OSS.Core仓储层设计实现

  4. 调用示例

ColumnAttributeTypeMapper辅助类相关代码如下:(如果不考虑实体类的属性与表字段不一致的情况,如下映射类可以不需要添加,同时SqlDapperUtil中移除相关依赖ColumnAttributeTypeMapper逻辑即可)

1、开源、轻量、小巧、上手容易。

   下边的实现部分中可能需要你对.NET的
泛型,委托,扩展,表达式等有一个基础了解。正是因为这些语言特性,方便我们对操作共性的抽取统一。

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<T> : FallbackTypeMapper
    {
        public ColumnAttributeTypeMapper()
            : base(new SqlMapper.ITypeMap[]
                {
                    new CustomPropertyTypeMap(
                       typeof(T),
                       (type, columnName) =>
                           type.GetProperties().FirstOrDefault(prop =>
                               prop.GetCustomAttributes(false)
                                   .OfType<ColumnAttribute>()
                                   .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<SqlMapper.ITypeMap> _mappers;

        public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> 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);
        }
    }

}

2、支持的数据库还蛮多的,
Mysql,SqlLite,Sqlserver,Oracle等一系列的数据库。

一. 数据仓储层需求

使用示例方法如下:

3、Dapper原理通过Emit反射IDataReader的序列队列来快速的得到和产生对象。性能貌似很牛逼的样子

  既然是一个完整的项目,数据访问是其最基本的部分,同时,数据访问也是整个项目最容易出现瓶颈的地方。在我的划分中,其承担的角色是负责整个数据的输入输出,不仅仅是针对单数据库(有时甚至多库),有时还需要完成一级缓存的实现,给逻辑层提供最基础的数据支撑。

 1.先来模拟各种查询数据(由于是直接写模拟SQL输出,故没有条件,也便于大家COPY后直接可以测试结果)

缺点:

 
 业务永远是在变化的,那么项目也要具备快速演进的能力,所以我希望数据层能够保持相对的简单,在结构上尽量减少复杂的耦合查询,在性能上尽量减少不必要的消耗,例如反射的大量使用。同时针对每个业务对象完成数据库层面基本的CRUD统一封装实现。如果有需要的时候还能在最少的改动下加入缓存的更新。(对于如何实现不同模块不同缓存存储策略,像Redis,Memcached会在后边文章介绍)

            //实例化SqlDapperUtil对象,构造函数是config文件中的connectionStrings的Name名
            var dapper = new SqlDapperUtil("LmsConnectionString");

            //查询1个值
            DateTime nowTime = dapper.GetValue<DateTime>("select getdate() as nowtime");


            //查询1行值,并转换成字典(这对于临时查询多个字段而无需定义实体类有用)
            Dictionary<string, dynamic> rowValues = dapper.GetFirstValues("select 0 as col0,1 as col1,2 as col2");


            //查询1行并返回实体类
            Person person = dapper.GetModel<Person>("select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr");


            //查询1行表字段与实体类属性不一致映射
            Person person2 = dapper.GetModel<Person>("select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddress");


            //查询多行返回实体集合
            var persons = dapper.GetModelList<Person>(@"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<Person>(@"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<Person> personWithManyCars = new List<Person>();
            dapper.GetMultModelList<Person>(@"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<CarInfo>();
                                                                                personItemMain = personItem;
                                                                                personWithManyCars.Add(personItemMain);
                                                                            }

                                                                            personItemMain.Cars.Add(carItem);
                                                                            return personItemMain;

                                                                        }, splitOn: "DriverName");

作为一款ORM太过于轻量级了,根据对象自动生成sql的功能还是空白,需要自己来扩展,

  同时,对于一个稍微有点规模的项目来说,解决数据库访问的最快速做法就是实现读写分离,所以,我希望这个框架能够在一开始在底层就实现了读写分离的支持,以避免后期再重头对业务代码的大量修改。  

2.下面是演示如何进行增、删、改以及动态查询的情况:

当然这也是优点, 好声音的导师们经常说某人就是张白纸……

二. ORM 框架选择

            //使用事务创建多张表,多条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<CarInfo>(@"
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<string, object>();

            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<string, int> hasCarInfo = dapper.GetDynamicModel<Tuple<string, int>>((rs) =>
            {
                var result = rs.First();
                return Tuple.Create<string, int>(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 = "张三" });

因此针对Dapper已经有很多成熟的扩展项目了,Dapper.Rainbow、Dapper.Contrib,DapperExtensions。

  当然,如果为了简单和性能,直接ADO.NET连接理论上来说是比较高效的做法,不过这样会造成大量的重复操作逻辑代码,同时也会造成代码的散乱,增加维护复杂度。作为技术人员,不仅需要解决业务问题提高效率,同时也要提高自己的效率,所以我会选择一个ORM框架来完成部分基础工作。

3.还有两个方法:BatchCopyData、BatchMoveData,这是特殊封装的,不是基于Dapper而是基于原生的Ado.net及BCP,目的是快速大量跨DB跨表COPY数据或转移数据,使用也不复杂,建议想了解的网友可以查看我以往的文章

我们这里介绍的是DapperExtensions

  当前在.NET体系下,开源的ORM框架很多,如:Entityframework,NHibernate,iBATIS.NET,Dapper等等,各有特色,基于前面我说的,保证效率的同时,兼顾简单还能最大程度减少性能的损耗,并且提供.net
standard标准库下的支持。这里对比之后我选择Dapper这个半自动化的ORM作为仓储层的基础框架,选择原因如下:

以上示例方法用到了两个类,如下:

dapper-dot-net源码:
(更新频率快,项目包含了各种除了Dapper-Extensions的扩展项目)

  1. 其结构简单,整个封装主要集中Dapper.cs文件中,体积很小

        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 List<CarInfo> Cars { get; set; }
        }

        class CarInfo
        {
            public string Brand { get; set; }

            public DateTime ManufactureDate { get; set; }

            public string DriverName { get; set; }
        }

Dapper-Extensions源码:

      2. 封装功能简单强大,对原生SQL的支持上很灵活

SqlDapperUtil类中依赖了之前我封装的类:如:MemoryCacheUtil(本地内存依赖缓存实用工具类)、ConfigUtil(配置文件管理工具类)、EncryptUtil(加密工具类),如果项目中不想引用这些类,可以移除或改成其它方法即可。

Dapper-Extensions的优点

    这点几乎完胜其他框架,无需任何多余的设置,同时基本上你可调用所有原生ADO.NET的功能,sql语句完全自己掌控,却又无需关心command的参数赋值,以及结果实体转换等。

 另外说明一下,为了防止和减少因DB连接未及时释放导致的连接池不足等原因,故默认执行所有的CRUD方法都是用完即释放,但有一种情况不会释放就是使用了事务,若使用事务,则必需配套使用:UseDbTransaction、Commit、或失败执行Rollback,否则可能导致未能及时释放对象,当然最终当SqlDapperUtil实例被回收后事务若没有提交或回滚,会强制执行回滚操作并释放事务及连接对象,防止可能的资源浪费情况。

1、开源

  3. 性能上的高效

本来早就想总结一下这篇文章,但一直由于工作太忙没有时间,今天利用加班研究.NET
CORE的空隙时间完成,请大家支持,有好东西我一定会分享的,虽然不一定高大上,但一定实用且项目中有实战过的。

2、针对Dapper封装了常用的CRUD方法,有独立的查询语法。

    很多ORM的实体映射通过反射来完成,这点上Dapper再次展现其魅力,在Commond参数赋值,以及实体转换等关键模块,使用了Reflection.Emit功能,间接实现了MSIL编译层面的赋值实现,之所以说间接,是因为其本身代码还需要编译器生成IL代码。在运行时根据类型属性动态创建赋值委托方法。

3、需要映射的实体类本身0配置,无需加特性什么的。是通过独立的映射类来处理,可以设置类映射到DB的别名,字段的别名等等。

 

Dapper-Extensions的缺点:

三. OSS.Core仓储层设计实现

1、好几年没更新了

 
 通过Dapper可以实现在数据库访问部分一层简单的封装,不过我依然需要手动编写不少的sql语句,同时还要进行参数化的处理,包括数据的读写分离等。那么这些功能的实现我将在OSS.Core.RepDapper中完成,为了方便理解,先贴出一个简单的封装后的方法调用传输流程:

2、不支持oracle(木有oracle的方言,已经搞定)

图片 1

3、不能同时支持多种数据库

  在这个图里展示一个简单的方法调用流程,围绕这张图的几个核心部分,我分别介绍下:

4、部分代码有些bug

  1. 接口设计

下面先简单介绍一下Dapper的基本语法。

  因为我希望这个是完整的示例项目,所以后边希望能够兼容不同数据库,因此对外的仓储访问都基于接口调用。当然如果你的项目根本没有切换数据库的需求,我更建议去掉这一环节,直接在基类中实现单例模式,业务逻辑层直接调用。

Dapper就一个.cs文件,可以放到项目代码中直接编译,也可以直接引用DLL文件。

  图中可以看到接口层独立于实现部分,我将具体业务实体模型和接口
单独放在了OSS.Core.DomainMos
类库中,一方面是为了实体模型在各模块中的共用,另一方面解耦业务逻辑层(Services)和仓储层(Reps)之间的依赖关系。

Dapper对DB的操作依赖于Connection,为了支持多库,咱们用IDbConnection conn

  同时一个项目中数据库访问代码多数都会以CRUD为主,所以这里我定义了一个基础接口(IBaseRep),其包含的方法主要有(表达式部分在后边介绍):

using (IDbConnection conn = GetConnection    {        const string query = "select * from XO order by id desc";        return conn.Query<XOEntity>(query,null);    }

图片 2

下面是带参数的语法

  具体的业务数据接口继承至基础接口就好,其中表达式部分是我自己做了一个封装,后边会简单介绍。

int xoID=666; //变量主键
 using (IDbConnection conn = GetConnection    {        const string query = "select * from XO where Id=@MyID";        return conn.Query<XOEntity>(query, new { MyID = xoID});                            }

 

各种方法都重载了事务的操作,一般的数据库操作都支持。但是每次执行都需要传递sql,而且每次都要使用Using,看着不爽啊,
这……

  2.
仓储基类实现(BaseRep)

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图