我偶然听说sqlsugar的性能比dapper强。对此我表示怀疑(由于我一直使用的dapper存在偏见吧),于是自己测试了sqlsugar、freesql、dapper发现他们的给我的结果是

sqlsugar>dapper>freesql(这里并不是黑那个orm,毕竟不同orm功能不同,底层实现不同,适用场景不同性能当然不同)。这让我很吃惊,dapper(号称orm king)一个执行sql的映射器,比不了基于linq语法的sqlsugar。同时也让我感到高兴,我高兴的是:orm的性能肯定还有提升的空间。

于是我便开始研究它们并着手编写。最终以一千行左右的代码量实现了dapper的基本映射功能,性能真正意义接近ado.net

对比于dapper的底层存在拆装箱操作(我的没有,请看IL),强制类型转换,dapper内置各种缓存(缓存就要考虑并发安全,就要用lock),许多功能并不是我们所需要的,一些功能又是我们迫切需要的,dapper有些定制化功能我们要查阅很多资料才能实现。浪费我们宝贵的时间,dapper对匿名类型支持并不好,这阻碍的我的另一个框架dapper.common(dapper的linq实现方案,将来要移植到sqlcommon),我让作者改一下,支持一下,作者认为linq映射也不是dapper所需要的功能,不予支持。

自己动手丰衣足食,那么我们完全可以自己编写一套。

性能测试

下面进行简要实现:

完整源码地址:https://github.com/1448376744/SqlCommon

nuget也发布了v1.0.0

我们要如何实现?我们只需要实现DataReader对象转实体类。我们需要用IL来动态创建下面的函数

  1. public T TypeConvert<T>(IDataReader reader)
  2. {
  3. var entity = new T();
  4. var index1 = reader.GetOrdinal("FieldName1");
  5. entity.FieldName1 = reader.GetString(index1);
  6. var index2 = reader.GetOrdinal("FieldName2");
  7. entity.FieldName2 = reader.GetString(index2);
  8. return entity;
  9. }

我们可以创建这样的函数,通过IL来动态创建,大致的过程

创建实体类型->判断实体类型中的属性在reader中是否存在->如果存在则对该字段赋值

我们定义一个接口,这个接口规范属性和字段的映射规则,类型转换规则,构造器规则,构造参数映射规则(可以有不同实现)

  1. public interface ITypeMapper
  2. {
  3. //根据字段信息,返回C#属性
  4. MemberInfo FindMember(MemberInfo[] properties, DbDataInfo dataInfo);
  5. //根据C#字段属性返回转换函数
  6. MethodInfo FindConvertMethod(Type csharpType);
  7. //处理匿名类型
  8. DbDataInfo FindConstructorParameter(DbDataInfo[] dataInfos, ParameterInfo parameterInfo);
  9. //根据目标类型返回构造器
  10. ConstructorInfo FindConstructor(Type csharpType);
  11. }

我们编写一个默认实现规则

  1. public class TypeMapper : ITypeMapper
  2. {
  3. //查找构造器
  4. public ConstructorInfo FindConstructor(Type csharpType)
  5. {
  6. var constructor = csharpType.GetConstructor(Type.EmptyTypes);
  7. if (constructor == null)
  8. {
  9. var constructors = csharpType.GetConstructors();
  10. constructor = constructors.Where(a => a.GetParameters().Length == constructors.Max(s => s.GetParameters().Length)).FirstOrDefault();
  11. }
  12. return constructor;
  13. }
    //构造参数映射规则
  14. public DbDataInfo FindConstructorParameter(DbDataInfo[] dataInfos, ParameterInfo parameterInfo)
  15. {
  16. foreach (var item in dataInfos)
  17. {
  18. if (item.DataName.Equals(parameterInfo.Name, StringComparison.OrdinalIgnoreCase))
  19. {
  20. return item;
  21. }
  22. else if (SqlMapper.MatchNamesWithUnderscores && item.DataName.Replace("_", "").Equals(parameterInfo.Name, StringComparison.OrdinalIgnoreCase))
  23. {
  24. return item;
  25. }
  26. }
  27. return null;
  28. }
    //查找属性
  29. public MemberInfo FindMember(MemberInfo[] properties, DbDataInfo dataInfo)
  30. {
  31. foreach (var item in properties)
  32. {
  33. if (item.Name.Equals(dataInfo.DataName, StringComparison.OrdinalIgnoreCase))
  34. {
  35. return item;//忽略大小写
  36. }
  37. else if (SqlMapper.MatchNamesWithUnderscores && item.Name.Equals(dataInfo.DataName.Replace("_", ""), StringComparison.OrdinalIgnoreCase))
  38. {
  39. return item;//忽略下划线
  40. }
  41. }
  42. return null;
  43. }
    //查找类型转换规则
  44. public MethodInfo FindConvertMethod(Type csharpType)
  45. {
  46.  
  47. if (csharpType == typeof(int) || Nullable.GetUnderlyingType(csharpType) == typeof(int))
  48. {
  49. return csharpType == typeof(int) ? DataConvertMethod.ToIn32Method : DataConvertMethod.ToIn32NullableMethod;
  50. }
  51. }
  52. }

然后实现一下DataConvertMethod(FindConvertMethod需要)这里是缩减版

  1. //你可以在这里编写json类型的转换策略,如果你的属性中有JObject类型的话
    public static class DataConvertMethod
  2. {
  3. /// <summary>
  4. /// int转换方法
  5. /// </summary>
  6. public static MethodInfo ToIn32Method = typeof(DataConvertMethod).GetMethod(nameof(DataConvertMethod.ConvertToInt32));
  7. /// <summary>
  8. /// int?转换方法
  9. /// </summary>
  10. public static MethodInfo ToIn32NullableMethod = typeof(DataConvertMethod).GetMethod(nameof(DataConvertMethod.ConvertToInt32Nullable));
  11. public static int ConvertToInt32(this IDataRecord dr, int i)
  12. {
  13. if (dr.IsDBNull(i))
  14. {
  15. return default;
  16. }
  17. var result = dr.GetInt32(i);
  18. return result;
  19. }
  20. public static int? ConvertToInt32Nullable(this IDataRecord dr, int i)
  21. {
  22. if (dr.IsDBNull(i))
  23. {
  24. return default;
  25. }
  26. var result = dr.GetInt32(i);
  27. return result;
  28. }
  29. }

然后我们编写IL来创建动态函数,并使用用上面的接口作为参数

  1. private static Func<IDataRecord, T> CreateTypeSerializerHandler<T>(ITypeMapper mapper, IDataRecord record)
  2. {
  3. var type = typeof(T);
  4. var dynamicMethod = new DynamicMethod($"{type.Name}Deserializer{Guid.NewGuid().ToString("N")}", type, new Type[] { typeof(IDataRecord) }, type, true);
  5. var generator = dynamicMethod.GetILGenerator();
  6. LocalBuilder local = generator.DeclareLocal(type);
  7. //获取到这个record中的所有字段信息
  8. var dataInfos = new DbDataInfo[record.FieldCount];
  9. for (int i = ; i < record.FieldCount; i++)
  10. {
  11. var dataname = record.GetName(i);
  12. var datatype = record.GetFieldType(i);
  13. var typename = record.GetDataTypeName(i);
  14. dataInfos[i] = new DbDataInfo(i, typename, datatype, dataname);
  15. }
  16. //查找构造器
  17. var constructor = mapper.FindConstructor(type);
  18. //获取所有属性
  19. var properties = type.GetProperties();
  20. //var entity = new T();
  21. generator.Emit(OpCodes.Newobj, constructor);
  22. generator.Emit(OpCodes.Stloc, local);
  23. //绑定属性
  24. foreach (var item in dataInfos)
  25. {
  26. //根据属性查找规则查找属性,如果不存在则不绑定
  27. var property = mapper.FindMember(properties, item) as PropertyInfo;
  28. if (property == null)
  29. {
  30. continue;
  31. }
  32. //获取转换成该字段类型的转换函数
  33. var convertMethod = mapper.FindConvertMethod(property.PropertyType);
  34. if (convertMethod == null)
  35. {
  36. continue;
  37. }
  38. //获取该C#字段,在本次查询的索引位
  39. int i = record.GetOrdinal(item.DataName);
  40. //下面这几行IL的意思是
  41. //entity.FieldName1 = reader.ConvertToInt32(i);
  42. generator.Emit(OpCodes.Ldloc, local);
  43. generator.Emit(OpCodes.Ldarg_0);
  44. generator.Emit(OpCodes.Ldc_I4, i);
  45. if (convertMethod.IsVirtual)
  46. generator.Emit(OpCodes.Callvirt, convertMethod);
  47. else
  48. generator.Emit(OpCodes.Call, convertMethod);
  49. generator.Emit(OpCodes.Callvirt, property.GetSetMethod());
  50. }
  51. // return entity;
  52. generator.Emit(OpCodes.Ldloc, local);
  53. generator.Emit(OpCodes.Ret);
  54. //创建成委托,参数IDataReader,返回T,
  55. return dynamicMethod.CreateDelegate(typeof(Func<IDataRecord, T>)) as Func<IDataRecord, T>;
  56. }

动态创建的IL绑定函数我们需要编写一个缓存策略(我们使用hash结构进行存储),一个目标类型可能生成多个绑定函数,这根据你sql返回的字段个数和顺序有关

定义hash结构的key

  1. private struct SerializerKey : IEquatable<SerializerKey>
  2. {
  3. private string[] Names { get; set; }
  4. private Type Type { get; set; }
  5. public override bool Equals(object obj)
  6. {
  7. return obj is SerializerKey && Equals((SerializerKey)obj);
  8. }
    //由于我们会查询不同个数的列,而使用同一个实体,个数不同生成的绑定IL函数也不同
    //所以同一个类型可能会生成多个绑定,因此重写equals
  9. public bool Equals(SerializerKey other)
  10. {
    //先判断目标类型
  11. if (Type != other.Type)
  12. {
  13. return false;
  14. }
    //判断字段个数
  15. else if (Names.Length != other.Names.Length)
  16. {
  17. return false;
  18. }
    //判断顺序
  19. else
  20. {
  21. for (int i = ; i < Names.Length; i++)
  22. {
  23. if (Names[i] != other.Names[i])
  24. {
  25. return false;
  26. }
  27. }
  28. return true;
  29. }
  30. }
  31. //根据类型进行hash存储。
  32. public override int GetHashCode()
  33. {
  34. return Type.GetHashCode();
  35. }
  36. public SerializerKey(Type type, string[] names)
  37. {
  38. Type = type;
  39. Names = names;
  40. }
  41. }

编写一个缓存策略

  1. /// <summary>
  2. /// 从缓存中读取类型转换器
  3. /// </summary>
  4. public static Func<IDataRecord, T> GetSerializer<T>(ITypeMapper mapper, IDataRecord record)
  5. {
  6. string[] names = new string[record.FieldCount];
  7. for (int i = ; i < record.FieldCount; i++)
  8. {
  9. names[i] = record.GetName(i);
  10. }
    //从缓存中读取
  11. var key = new SerializerKey(typeof(T), names);
  12. _serializers.TryGetValue(key, out object handler);
  13. if (handler == null)
  14. {
    //这里在写的时候才开始lock,而dapper是在读的时候,我认为那样对并发有影响,不能因为你的框架要做缓存,就影响到我并发
    //而我在写的时候才锁,只影响你第一次
  15. lock (_serializers)
  16. {
  17. handler = CreateTypeSerializerHandler<T>(mapper, record);
  18. if (!_serializers.ContainsKey(key))
  19. {
  20. _serializers.Add(key, handler);
  21. }
  22. }
  23. }
  24. return handler as Func<IDataRecord, T>;
  25. }

好了大部分工作都完成了,我们编一个sql执行器(简化版)

  1. public static IEnumerable<T> ExecuteQuery<T>(this IDbConnection connection, string sql)
  2. {
  3. if (connection.State == ConnectionState.Closed)
  4. connection.Open();
  5. using (var cmd = connection.CreateCommand())
  6. {
  7. cmd.CommandText = sql;
  8. using (var reader = cmd.ExecuteReader())
  9. {
  10. var handler = TypeConvert.GetSerializer<T>(reader);
  11. while (reader.Read())
  12. {
  13. yield return handler(reader);
  14. }
  15. }
  16. }
  17. }

至此我们已经完成了整个流程。

我们可以发现没有拆装箱,没有强制类型转换,

对比于使用ado.net的性能差距,由于我们的动态生成绑定函数,在下次使用的时候我们需要从hash表中去查询这个函数指针。

这便是性能的差距点,而我们首先绑定函数,下次时候的时候显示的调用你定义的绑定函数。

也就是说,你只要能优化这个缓存策略,就能无限接近手写ado.net。

从0开始编写dapper核心功能、压榨性能、自己动手丰衣足食的更多相关文章

  1. ES6,ES2105核心功能一览,js新特性详解

    ES6,ES2105核心功能一览,js新特性详解 过去几年 JavaScript 发生了很大的变化.ES6(ECMAScript 6.ES2105)是 JavaScript 语言的新标准,2015 年 ...

  2. Spring框架的IOC核心功能快速入门

    2. 步骤一:下载Spring框架的开发包 * 官网:http://spring.io/ * 下载地址:http://repo.springsource.org/libs-release-local/ ...

  3. Spring框架的核心功能之AOP技术

     技术分析之Spring框架的核心功能之AOP技术 AOP的概述        1. 什么是AOP的技术?        * 在软件业,AOP为Aspect Oriented Programming的 ...

  4. tfgan折腾笔记(一):核心功能简要概述

    tfgan是什么? tfgan是tensorflow团队开发出的一个专门用于训练各种GAN的轻量级库,它是基于tensorflow开发的,所以兼容于tensorflow.在tensorflow1.x版 ...

  5. 【java框架】MyBatis-Plus(1)--MyBatis-Plus快速上手开发及核心功能体验

    1.MyBatis-Plus入门开发及配置 1.1.MyBatis-Plus简介 MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变, ...

  6. Chrome扩展开发之四——核心功能的实现思路

    目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...

  7. discuz论坛apache日志hadoop大数据分析项目:清洗数据核心功能解说及代码实现

    discuz论坛apache日志hadoop大数据分析项目:清洗数据核心功能解说及代码实现http://www.aboutyun.com/thread-8637-1-1.html(出处: about云 ...

  8. Shiro 核心功能案例讲解 基于SpringBoot 有源码

    Shiro 核心功能案例讲解 基于SpringBoot 有源码 从实战中学习Shiro的用法.本章使用SpringBoot快速搭建项目.整合SiteMesh框架布局页面.整合Shiro框架实现用身份认 ...

  9. Python交互K线工具 K线核心功能+指标切换

    Python交互K线工具 K线核心功能+指标切换 aiqtt团队量化研究,用vn.py回测和研究策略.基于vnpy开源代码,刚开始接触pyqt,开发界面还是很痛苦,找了很多案例参考,但并不能完全满足我 ...

随机推荐

  1. java 使用网建SMS发送短信验证码

    首先, 注册并登录网建用户, 新注册用户将获得5条的测试短信 网建短信通地址: http://sms.webchinese.cn/default.shtml 注册账号在此就不多做赘述了, 直接上代码 ...

  2. TCP 通信时序及状态变迁

    TCP 通信时序及状态变迁 参考链接: https://www.cnblogs.com/boxker/p/11214886.html https://blog.csdn.net/miss_ruoche ...

  3. cocoapods升级

    1.更新gem:sudo gem update --system 先要查看下源,如果源被墙了就换地址:https://gems.ruby-china.com 1.1.删除gem源:gem source ...

  4. CNN是怎样一步步工作的?

    非常形象详细的博客:链接1 链接2 为了完成我们的卷积,我们不断地重复着上述过程,将feature和图中每一块进行卷积操作.最后通过每一个feature的卷积操作,我们会得到一个新的二维数组.这也可以 ...

  5. python yield实现协程(生产者-消费者)

    def customer(): r="" while True: n=yield r#,接收生产者的消息,并向消费者发送r print("customer receive ...

  6. Kali系统改国内源配置和SSH配置

    一.Kali系统更新源 使用官网的虚拟化镜像安装,默认为英文界面,更新源也是官方源.因为官方服务器在国外,速度不是很理想,现在就来改国内源并且更新系统. 1.使用编辑器打开系统源文本(在终端内操作,先 ...

  7. 浅谈HTTPS传输过程

    HTTPS是什么 HTTPS不是一个新的协议,可以理解为是一个HTTP协议的加密"版本"(HTTP+SSL(TLS)).那为什么HTTP协议需要加密,不加密会出现什么问题呢?先来了 ...

  8. mysql 包含查找

    #从表iot_company选择,company_name字段包含10091015的项SELECT id FROM iot_company WHERE company_name LIKE " ...

  9. keepass口令管理实践

    语言修改 加入插件 插件的学习及应用

  10. 关于央行数字货币DCEP的几个特点的思考(转)

    近期,央行即将推出数字货币,无论在金融领域还是在资本市场,央行数字货币这一话题都被炒的很火热.央行研发的数字货币叫做DCEP(DC,DigitalCurrency,是数字货币:EP,Electroni ...