从0开始编写dapper核心功能、压榨性能、自己动手丰衣足食
我偶然听说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来动态创建下面的函数
public T TypeConvert<T>(IDataReader reader)
{
var entity = new T();
var index1 = reader.GetOrdinal("FieldName1");
entity.FieldName1 = reader.GetString(index1);
var index2 = reader.GetOrdinal("FieldName2");
entity.FieldName2 = reader.GetString(index2);
return entity;
}
我们可以创建这样的函数,通过IL来动态创建,大致的过程
创建实体类型->判断实体类型中的属性在reader中是否存在->如果存在则对该字段赋值
我们定义一个接口,这个接口规范属性和字段的映射规则,类型转换规则,构造器规则,构造参数映射规则(可以有不同实现)
public interface ITypeMapper
{
//根据字段信息,返回C#属性
MemberInfo FindMember(MemberInfo[] properties, DbDataInfo dataInfo);
//根据C#字段属性返回转换函数
MethodInfo FindConvertMethod(Type csharpType);
//处理匿名类型
DbDataInfo FindConstructorParameter(DbDataInfo[] dataInfos, ParameterInfo parameterInfo);
//根据目标类型返回构造器
ConstructorInfo FindConstructor(Type csharpType);
}
我们编写一个默认实现规则
public class TypeMapper : ITypeMapper
{
//查找构造器
public ConstructorInfo FindConstructor(Type csharpType)
{
var constructor = csharpType.GetConstructor(Type.EmptyTypes);
if (constructor == null)
{
var constructors = csharpType.GetConstructors();
constructor = constructors.Where(a => a.GetParameters().Length == constructors.Max(s => s.GetParameters().Length)).FirstOrDefault();
}
return constructor;
}
//构造参数映射规则
public DbDataInfo FindConstructorParameter(DbDataInfo[] dataInfos, ParameterInfo parameterInfo)
{
foreach (var item in dataInfos)
{
if (item.DataName.Equals(parameterInfo.Name, StringComparison.OrdinalIgnoreCase))
{
return item;
}
else if (SqlMapper.MatchNamesWithUnderscores && item.DataName.Replace("_", "").Equals(parameterInfo.Name, StringComparison.OrdinalIgnoreCase))
{
return item;
}
}
return null;
}
//查找属性
public MemberInfo FindMember(MemberInfo[] properties, DbDataInfo dataInfo)
{
foreach (var item in properties)
{
if (item.Name.Equals(dataInfo.DataName, StringComparison.OrdinalIgnoreCase))
{
return item;//忽略大小写
}
else if (SqlMapper.MatchNamesWithUnderscores && item.Name.Equals(dataInfo.DataName.Replace("_", ""), StringComparison.OrdinalIgnoreCase))
{
return item;//忽略下划线
}
}
return null;
}
//查找类型转换规则
public MethodInfo FindConvertMethod(Type csharpType)
{ if (csharpType == typeof(int) || Nullable.GetUnderlyingType(csharpType) == typeof(int))
{
return csharpType == typeof(int) ? DataConvertMethod.ToIn32Method : DataConvertMethod.ToIn32NullableMethod;
}
}
}
然后实现一下DataConvertMethod(FindConvertMethod需要)这里是缩减版
//你可以在这里编写json类型的转换策略,如果你的属性中有JObject类型的话
public static class DataConvertMethod
{
/// <summary>
/// int转换方法
/// </summary>
public static MethodInfo ToIn32Method = typeof(DataConvertMethod).GetMethod(nameof(DataConvertMethod.ConvertToInt32));
/// <summary>
/// int?转换方法
/// </summary>
public static MethodInfo ToIn32NullableMethod = typeof(DataConvertMethod).GetMethod(nameof(DataConvertMethod.ConvertToInt32Nullable));
public static int ConvertToInt32(this IDataRecord dr, int i)
{
if (dr.IsDBNull(i))
{
return default;
}
var result = dr.GetInt32(i);
return result;
}
public static int? ConvertToInt32Nullable(this IDataRecord dr, int i)
{
if (dr.IsDBNull(i))
{
return default;
}
var result = dr.GetInt32(i);
return result;
}
}
然后我们编写IL来创建动态函数,并使用用上面的接口作为参数
private static Func<IDataRecord, T> CreateTypeSerializerHandler<T>(ITypeMapper mapper, IDataRecord record)
{
var type = typeof(T);
var dynamicMethod = new DynamicMethod($"{type.Name}Deserializer{Guid.NewGuid().ToString("N")}", type, new Type[] { typeof(IDataRecord) }, type, true);
var generator = dynamicMethod.GetILGenerator();
LocalBuilder local = generator.DeclareLocal(type);
//获取到这个record中的所有字段信息
var dataInfos = new DbDataInfo[record.FieldCount];
for (int i = ; i < record.FieldCount; i++)
{
var dataname = record.GetName(i);
var datatype = record.GetFieldType(i);
var typename = record.GetDataTypeName(i);
dataInfos[i] = new DbDataInfo(i, typename, datatype, dataname);
}
//查找构造器
var constructor = mapper.FindConstructor(type);
//获取所有属性
var properties = type.GetProperties();
//var entity = new T();
generator.Emit(OpCodes.Newobj, constructor);
generator.Emit(OpCodes.Stloc, local);
//绑定属性
foreach (var item in dataInfos)
{
//根据属性查找规则查找属性,如果不存在则不绑定
var property = mapper.FindMember(properties, item) as PropertyInfo;
if (property == null)
{
continue;
}
//获取转换成该字段类型的转换函数
var convertMethod = mapper.FindConvertMethod(property.PropertyType);
if (convertMethod == null)
{
continue;
}
//获取该C#字段,在本次查询的索引位
int i = record.GetOrdinal(item.DataName);
//下面这几行IL的意思是
//entity.FieldName1 = reader.ConvertToInt32(i);
generator.Emit(OpCodes.Ldloc, local);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, i);
if (convertMethod.IsVirtual)
generator.Emit(OpCodes.Callvirt, convertMethod);
else
generator.Emit(OpCodes.Call, convertMethod);
generator.Emit(OpCodes.Callvirt, property.GetSetMethod());
}
// return entity;
generator.Emit(OpCodes.Ldloc, local);
generator.Emit(OpCodes.Ret);
//创建成委托,参数IDataReader,返回T,
return dynamicMethod.CreateDelegate(typeof(Func<IDataRecord, T>)) as Func<IDataRecord, T>;
}
动态创建的IL绑定函数我们需要编写一个缓存策略(我们使用hash结构进行存储),一个目标类型可能生成多个绑定函数,这根据你sql返回的字段个数和顺序有关
定义hash结构的key
private struct SerializerKey : IEquatable<SerializerKey>
{
private string[] Names { get; set; }
private Type Type { get; set; }
public override bool Equals(object obj)
{
return obj is SerializerKey && Equals((SerializerKey)obj);
}
//由于我们会查询不同个数的列,而使用同一个实体,个数不同生成的绑定IL函数也不同
//所以同一个类型可能会生成多个绑定,因此重写equals
public bool Equals(SerializerKey other)
{
//先判断目标类型
if (Type != other.Type)
{
return false;
}
//判断字段个数
else if (Names.Length != other.Names.Length)
{
return false;
}
//判断顺序
else
{
for (int i = ; i < Names.Length; i++)
{
if (Names[i] != other.Names[i])
{
return false;
}
}
return true;
}
}
//根据类型进行hash存储。
public override int GetHashCode()
{
return Type.GetHashCode();
}
public SerializerKey(Type type, string[] names)
{
Type = type;
Names = names;
}
}
编写一个缓存策略
/// <summary>
/// 从缓存中读取类型转换器
/// </summary>
public static Func<IDataRecord, T> GetSerializer<T>(ITypeMapper mapper, IDataRecord record)
{
string[] names = new string[record.FieldCount];
for (int i = ; i < record.FieldCount; i++)
{
names[i] = record.GetName(i);
}
//从缓存中读取
var key = new SerializerKey(typeof(T), names);
_serializers.TryGetValue(key, out object handler);
if (handler == null)
{
//这里在写的时候才开始lock,而dapper是在读的时候,我认为那样对并发有影响,不能因为你的框架要做缓存,就影响到我并发
//而我在写的时候才锁,只影响你第一次
lock (_serializers)
{
handler = CreateTypeSerializerHandler<T>(mapper, record);
if (!_serializers.ContainsKey(key))
{
_serializers.Add(key, handler);
}
}
}
return handler as Func<IDataRecord, T>;
}
好了大部分工作都完成了,我们编一个sql执行器(简化版)
public static IEnumerable<T> ExecuteQuery<T>(this IDbConnection connection, string sql)
{
if (connection.State == ConnectionState.Closed)
connection.Open();
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
var handler = TypeConvert.GetSerializer<T>(reader);
while (reader.Read())
{
yield return handler(reader);
}
}
}
}
至此我们已经完成了整个流程。
我们可以发现没有拆装箱,没有强制类型转换,
对比于使用ado.net的性能差距,由于我们的动态生成绑定函数,在下次使用的时候我们需要从hash表中去查询这个函数指针。
这便是性能的差距点,而我们首先绑定函数,下次时候的时候显示的调用你定义的绑定函数。
也就是说,你只要能优化这个缓存策略,就能无限接近手写ado.net。
从0开始编写dapper核心功能、压榨性能、自己动手丰衣足食的更多相关文章
- ES6,ES2105核心功能一览,js新特性详解
ES6,ES2105核心功能一览,js新特性详解 过去几年 JavaScript 发生了很大的变化.ES6(ECMAScript 6.ES2105)是 JavaScript 语言的新标准,2015 年 ...
- Spring框架的IOC核心功能快速入门
2. 步骤一:下载Spring框架的开发包 * 官网:http://spring.io/ * 下载地址:http://repo.springsource.org/libs-release-local/ ...
- Spring框架的核心功能之AOP技术
技术分析之Spring框架的核心功能之AOP技术 AOP的概述 1. 什么是AOP的技术? * 在软件业,AOP为Aspect Oriented Programming的 ...
- tfgan折腾笔记(一):核心功能简要概述
tfgan是什么? tfgan是tensorflow团队开发出的一个专门用于训练各种GAN的轻量级库,它是基于tensorflow开发的,所以兼容于tensorflow.在tensorflow1.x版 ...
- 【java框架】MyBatis-Plus(1)--MyBatis-Plus快速上手开发及核心功能体验
1.MyBatis-Plus入门开发及配置 1.1.MyBatis-Plus简介 MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变, ...
- Chrome扩展开发之四——核心功能的实现思路
目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...
- discuz论坛apache日志hadoop大数据分析项目:清洗数据核心功能解说及代码实现
discuz论坛apache日志hadoop大数据分析项目:清洗数据核心功能解说及代码实现http://www.aboutyun.com/thread-8637-1-1.html(出处: about云 ...
- Shiro 核心功能案例讲解 基于SpringBoot 有源码
Shiro 核心功能案例讲解 基于SpringBoot 有源码 从实战中学习Shiro的用法.本章使用SpringBoot快速搭建项目.整合SiteMesh框架布局页面.整合Shiro框架实现用身份认 ...
- Python交互K线工具 K线核心功能+指标切换
Python交互K线工具 K线核心功能+指标切换 aiqtt团队量化研究,用vn.py回测和研究策略.基于vnpy开源代码,刚开始接触pyqt,开发界面还是很痛苦,找了很多案例参考,但并不能完全满足我 ...
随机推荐
- Nginx 核心配置-单节点实现多域名访问
Nginx 核心配置-单节点实现多域名访问 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.试验环境说明 1>.虚拟机环境说明 [root@node101.yinzheng ...
- Python实现感知器的逻辑电路(与门、与非门、或门、异或门)
在神经网络入门回顾(感知器.多层感知器)中整理了关于感知器和多层感知器的理论,这里实现关于与门.与非门.或门.异或门的代码,以便对感知器有更好的感觉. 此外,我们使用 pytest 框架进行测试. p ...
- ZJOI2019赛季回顾
退役了. NOIP2018 day1没什么好说的. day2开考后看完题:这个T3 TM不是DDP吗? 考前刚学过这东西,还没去写过 当时不知道在想什么,胡了T1 60和T2 50分保底之后就去刚T3 ...
- shell脚本显示字体颜色
shell脚本中echo显示内容带颜色显示,echo显示带颜色,需要使用参数-e 格式如下: echo -e "\033[字背景颜色:文字颜色m字符串\033[0m" 例如: ec ...
- app内嵌h5分享到小程序分享功能
if (this.GLOBAL.env !== 'production') { try { window.JSBridge.shareMiniProgramToWx('https://www.lexi ...
- CF 494E Sharti
CF 494E Sharti 题意:一个\(n \times n\)的棋盘,共有m个矩形中的格子为白色.两个人需要博弈,每次操作选择一个边长不超过k的正方形并翻转颜色,每次翻转需要正方形的右下角为白色 ...
- JVM 发生内存溢出的 8 种原因、及解决办法
阅读本文大概需要 2.3 分钟. 出处:割肉机 cnblogs.com/williamjie/p/11164572.html Java 堆空间 GC 开销超过限制 请求的数组大小超过虚拟机限制 Per ...
- Java集合详解1:一文读懂ArrayList,Vector与Stack使用方法和实现原理
本文非常详尽地介绍了Java中的三个集合类 ArrayList,Vector与Stack <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整 ...
- 提取文件中的每一个mask,并将mask命名为文件名字
import cv2 as cv import random import glob import os from PIL import Image import shutil def get_sam ...
- Elasticsearch运维经验总结
Elasticsearch运维经验总结 2018年12月10日 16:38:41 运小白 阅读数 3811 版本说明:5.6.4(要严格注意ES及其插件.第三方工具的版本匹配关系) 系统负载:(日 ...