Dapper源码学习和源码修改(下篇)
目录:
Dapper源码学习和源码修改(上篇主要讲解入参解析)
Dapper源码学习和源码修改(下篇主要讲解出参解析)
继上篇讲了下自己学习Dapper的心得之后,下篇也随之而来,上篇主要讲的入参解析那下篇自然主打出参映射了。
好了,废话不多说,开始吧。
学习之前你的先学习怎么使用Dapper,这个我在上篇都提过,如果没使用过Dapper的同学,先去看看怎么使用吧,我这也简单贴一部分代码吧。
使用查询的Demo
//查询
sql = "select * from Teacher";
var list = SqlMapper.Query<Teacher>(conn, sql, null).ToList(); sql = "select * from Teacher left join Student on Teacher.Id=Student.Tid";
//一对一
var list1 = SqlMapper.Query<Teacher, Student, Teacher>(conn, sql,
(t, s) =>
{
if (t.Student == null) t.Student = new List<Student>();
t.Student.Add(s);
return t;
}
, null, true, null, "Id", null, null);
//一对多
Dictionary<string, Teacher> list2Dict = new Dictionary<string, Teacher>();//这个才是最后的结果
var list2 = SqlMapper.Query<Teacher, Student, Teacher>(conn, sql,
(t, s) =>
{
Teacher temp;
if (!list2Dict.TryGetValue(t.Id, out temp))
{
temp = t;
list2Dict.Add(temp.Id, temp);
}
if (temp.Student == null) temp.Student = new List<Student>();
temp.Student.Add(s);
return temp;
}
, null, true, null, "Id", null, null);
好了,我也不解释,自己体会。
我们先看看Dapper提供了哪些对外的查询方法呢,既然是将出参,只有查询才会涉及到DataReader转实体的呢,所以主要就看那几个查询方法就行了。

前两个是一个实体映射的,后面三个是多个实体映射,正常情况下一个实体对应一个表,你也可以一个表对应多个实体也是可行的。
由浅入深,先看单个实体的查询。
private static IEnumerable<T> QueryInternal<T>(IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType)
{
var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity); using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType))
{
using (var reader = cmd.ExecuteReader())
{
Func<Func<IDataReader, object>> cacheDeserializer = delegate()
{
info.Deserializer = GetDeserializer(typeof(T), reader, 0, -1, false);
SetQueryCache(identity, info);
return info.Deserializer;
}; if (info.Deserializer == null)
{
cacheDeserializer();
} var deserializer = info.Deserializer; while (reader.Read())
{
object next;
try
{
next = deserializer(reader);
}
catch (DataException)
{
deserializer = cacheDeserializer();
next = deserializer(reader);
}
yield return (T)next;
}
}
}
}
这个就是单个实体的查询方法,用一张图片说明

很明显在读取reader的时候 next = deserializer(reader); 就是这个将reader转成实体的,那这个deserializer是什么呢,往上看啊,上面重点二字的地方就是创建deserializer 委托的地方,对了这里插一句这里委托Func(有返回值的泛型委托),之前在入参讲解的时候那里委托是Action(无返回值的泛型委托)。
也就是说deserializer就是创建委托的地方,我们去看看它的庐山真面目。
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
Func<IDataReader, object> func = null;
if (IsSimpleValue(type))
{
func = GetSimpleDeserializer(type, startBound);
}
else if (typeof(IDictionary).IsAssignableFrom(type))
{
func = GetDictionaryDeserializer(type, startBound);
}
else if (type.IsClass)
{
func = GetClassDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
}
return func;
}
func = GetSimpleDeserializer(type, startBound); func = GetDictionaryDeserializer(type, startBound); 这两个是我扩展的两种类型,就是为了让出参支持简单类型和继承IDictionary的类型。
而 func = GetClassDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); 这个才是重点中的难点,这个就是将reader转成实体的委托。
private static Func<IDataReader, object> GetClassDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), type, new[] { typeof(IDataReader) }, true); var il = dm.GetILGenerator();
il.DeclareLocal(typeof(int));
il.DeclareLocal(type);
bool haveEnumLocal = false;
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0);
List<PropertyInfo> properties = GetSettableProps(type);
List<FieldInfo> fields = GetSettableFields(type);
if (length == -1)
{
length = reader.FieldCount - startBound;
} if (reader.FieldCount <= startBound)
{
throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
} var names = new List<string>(); for (int i = startBound; i < startBound + length; i++)
{
names.Add(reader.GetName(i));
}
var setters = new List<DynamicSetter>(); foreach (var name in names)
{
PropertyInfo p = FirstOrDefault(properties, new Func<PropertyInfo, bool>(delegate(PropertyInfo pro) { return Equals(pro.Name, name, StringComparison.Ordinal); }))
?? FirstOrDefault(properties, new Func<PropertyInfo, bool>(delegate(PropertyInfo pro) { return Equals(pro.Name, name, StringComparison.OrdinalIgnoreCase); }));
FieldInfo f = FirstOrDefault(fields, new Func<FieldInfo, bool>(delegate(FieldInfo fin) { return Equals(fin.Name, name, StringComparison.Ordinal); }))
?? FirstOrDefault(fields, new Func<FieldInfo, bool>(delegate(FieldInfo fin) { return Equals(fin.Name, name, StringComparison.OrdinalIgnoreCase); }));
setters.Add(new DynamicSetter { Name = name, Property = p, Field = f });
} int index = startBound; il.BeginExceptionBlock();
// stack is empty
il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)); // stack is now [target]
bool first = true;
var allDone = il.DefineLabel();
foreach (var item in setters)
{
if (item.Property != null || item.Field != null)
{
il.Emit(OpCodes.Dup); // stack is now [target][target]
Label isDbNullLabel = il.DefineLabel();
Label finishLabel = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
EmitInt32(il, index); // stack is now [target][target][reader][index]
il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] Type memberType = item.Property != null ? item.Property.PropertyType : item.Field.FieldType; if (memberType == typeof(char) || memberType == typeof(char?))
{
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(
memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
}
else
{
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object] // unbox nullable enums as the primitive, i.e. byte etc var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType; if (unboxType.IsEnum)
{
if (!haveEnumLocal)
{
il.DeclareLocal(typeof(string));
haveEnumLocal = true;
} Label isNotString = il.DefineLabel();
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
il.Emit(OpCodes.Stloc_2); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object] il.Emit(OpCodes.Pop); // stack is now [target][target] il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object] il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType }));
}
if (item.Property != null)
{
var setter = item.Property.DeclaringType == type ? item.Property.GetSetMethod(true) : item.Property.DeclaringType.GetProperty(item.Property.Name).GetSetMethod(true);
il.Emit(OpCodes.Callvirt, setter); // stack is now [target]
}
else
{
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
}
il.Emit(OpCodes.Br_S, finishLabel); il.MarkLabel(isNotString);
}
//if (memberType == typeof(System.Data.Linq.Binary))
//{
// il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
// il.Emit(OpCodes.Newobj, typeof(System.Data.Linq.Binary).GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
//}
//else
//{
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
//}
if (nullUnderlyingType != null && nullUnderlyingType.IsEnum)
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType }));
}
}
if (item.Property != null)
{
var setter = item.Property.DeclaringType == type ? item.Property.GetSetMethod(true) : item.Property.DeclaringType.GetProperty(item.Property.Name).GetSetMethod(true);
il.Emit(OpCodes.Callvirt, setter); // stack is now [target]
}
else
{
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
} il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value] il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Pop); // stack is now [target] if (first && returnNullIfFirstMissing)
{
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldnull); // stack is now [null]
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Br, allDone);
} il.MarkLabel(finishLabel);
}
first = false;
index += 1;
}
il.Emit(OpCodes.Stloc_1); // stack is empty
il.MarkLabel(allDone);
il.BeginCatchBlock(typeof(Exception)); // stack is Exception
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Stloc_1); // to make it verifiable
il.EndExceptionBlock(); il.Emit(OpCodes.Ldloc_1); // stack is empty
il.Emit(OpCodes.Ret); return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
}
呵呵,我也不多说,这个就是Dapper核心价值所在,使用Emit创建实体。
上面不是说到,我也扩展出参支持两种类型嘛,使用的时候就是这样
其一:IDictionary
sql = "select * from student";// where Id='916a84c6-85cb-4b41-b52a-96a0685d91b5'";
var sex = SqlMapper.Query<Dictionary<string, object>>(conn, sql, null).ToList();
很简单的将结果转成 Dictionary ,它的委托实现如下:
private static Func<IDataReader, object> GetDictionaryDeserializer(Type type, int index)
{
return delegate(IDataReader r)
{
IDictionary ht = Activator.CreateInstance(type) as IDictionary;
for (int i = 0; i < r.FieldCount; i++)
{
ht.Add(r.GetName(i), r[i]);
}
return ht;
};
}
其二:SimpleValue
何为SimpleValue呢,看下面
private static bool IsSimpleValue(Type type)
{
if (
type.IsEnum ||
isSame(type, typeof(byte)) || isSame(type, typeof(byte?)) ||
isSame(type, typeof(sbyte)) || isSame(type, typeof(sbyte?)) ||
isSame(type, typeof(long)) || isSame(type, typeof(long?)) ||
isSame(type, typeof(ulong)) || isSame(type, typeof(ulong?)) ||
isSame(type, typeof(short)) || isSame(type, typeof(short?)) ||
isSame(type, typeof(ushort)) || isSame(type, typeof(ushort?)) ||
isSame(type, typeof(int)) || isSame(type, typeof(int?)) ||
isSame(type, typeof(uint)) || isSame(type, typeof(uint?)) ||
isSame(type, typeof(float)) || isSame(type, typeof(float?)) ||
isSame(type, typeof(double)) || isSame(type, typeof(double?)) ||
isSame(type, typeof(decimal)) || isSame(type, typeof(decimal?)) ||
isSame(type, typeof(char)) || isSame(type, typeof(char?)) ||
isSame(type, typeof(bool)) || isSame(type, typeof(bool?)) ||
isSame(type, typeof(DateTime)) || isSame(type, typeof(DateTime?)) ||
isSame(type, typeof(string)) || isSame(type, typeof(object))
)
return true;
else
return false;
}
如果是上面的类型,我就会用下面的委托来转换reader
private static Func<IDataReader, object> GetSimpleDeserializer(Type type, int index)
{
return delegate(IDataReader r)
{
object obj = r.GetValue(index);
if (obj == null || (obj is DBNull)) return type.IsValueType ? Activator.CreateInstance(type) : null;
else
{
if (type.IsEnum)
obj = Convert.ChangeType(obj, typeof(int));
else
obj = Convert.ChangeType(obj, type);
return obj;
}
};
}
后续,上面只是讲到单个实体查询的情况,至于多个实体的查询,只需理解两个参数
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, bool buffered, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType)
这两个 splitOn 和 map 如果理解了这两个参数,其他跟单个实体是一样,至于这两个参数我先不讲解,后面我会提供源码,自己看,或者以后抽空我再讲讲。
总结:
出参就讲到这吧,后续我看讲不讲Dapper扩展的,会提供源码下载,源码可能和文章会有些出入,发布文章后我修改过源码。
源码下载:
Dapper源码学习和源码修改(下篇)的更多相关文章
- Dapper源码学习和源码修改
之前ORM比较火热,自己也搞了个WangSql,但是感觉比较low,大家都说Dapper性能好,所以现在学习学习Dapper,下面简单从宏观层面讲讲我学习的Dapper. 再了解一个东西前,先得学会使 ...
- Spring的学习和源码的学习
PS:Spring中有各种的Templeate,比如jdncTemplate,主要是为了避免各种模板的代码,抽象出来的 PS: @Configration.@Bean是用来替代xml那种解析方式 PS ...
- Unsafe 学习和源码阅读
在代码中获取 Unsafe 对象的方法: // 在 AtomicInteger 里面是这么用的private static final Unsafe unsafe = Unsafe.getUnsafe ...
- Java学习-039-源码 jar 包的二次开发扩展实例(源码修改)
最近在使用已有的一些 jar 包时,发现有些 jar 包中的一些方法无法满足自己的一些需求,例如返回固定的格式,字符串处理等等,因而需要对原有 jar 文件中对应的 class 文件进行二次开发扩展, ...
- ABP框架源码学习之修改默认数据库表前缀或表名称
ABP框架源码学习之修改默认数据库表前缀或表名称 1,源码 namespace Abp.Zero.EntityFramework { /// <summary> /// Extension ...
- Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
随机推荐
- 5分钟快速入门 - Less
下面给大家讲解下Less,纯手工,入门级别,相信没学过的人阅读完后就懂了,以下是我要讲的四点: 简单介绍 Less CSS 是一个使用广泛的 CSS 预处理器. 对 CSS 进行扩展,减少很多 CSS ...
- 如何创建DLL文件
动态链接库(DLL)是从C语言函数库和Pascal库单元的概念发展而来的.所有的C语言标准库函数都存放在某一函数库中.在链接应用程序的过程中,链接器从库文件中拷贝程序调用的函数代码,并把这些函数代码添 ...
- Android项目实战(三十一):异步下载apk文件并安装(非静默安装)
前言: 实现异步下载apk文件 并 安装.(进度条对话框显示下载进度的展现方式) 涉及技术点: 1.ProgressDialog 进度条对话框 用于显示下载进度 2.AsyncTask ...
- 实际情况来看,还是yield很爽
0 引言 最近公司有一个 php 的项目,要 port 到 node.js 来.我之前没有接触过这个项目,整个项目使用的是 yaf 框架.整个项目流程是调用服务端的业务数据,然后拼装数据,返回给前端: ...
- 谈谈事件对象-event
JavaScript 中的事件对象(event) 当我们每次触发一种事件(如点击事件),我们会在回调函数中传入事件对象event.今天就来来谈谈. 1.当我们想判断当前事件是我们想要的事件类型时,可以 ...
- Spring+SpringMVC+MyBatis+easyUI整合基础篇(六)maven整合SSM
写在前面的话 承接前文<Spring+SpringMVC+MyBatis+easyUI整合基础篇(五)讲一下maven>,本篇所讲述的是如何使用maven与原ssm项目整合,使得一个普 ...
- mvc4中的过滤器
过滤器(Filter)把附加逻辑注入到MVC框架的请求处理.实现了交叉关注. 交叉关注:用于整个应用程序,又不适合放在某个局部位置的功能. 过滤器是.NET的注解属性(Attribute),它们对请求 ...
- vSphere在RedHat6.0上搭建Oracle 11g R2 RAC环境
一.前期准备工作 1.1 为方便操作,装完系统后我们先安装Vmware Tools: 1.1.1.安装工具 在VMware的菜单栏上选择"虚拟机/安装虚拟机工具(VM/Install VMw ...
- redux-form的学习笔记二--实现表单的同步验证
(注:这篇博客参考自redux-form的官方英文文档)左转http://redux-form.com/6.5.0/examples/syncValidation/ 在这篇博客里,我将用redux-f ...
- 关于IE低版本兼容问题
1,元素浮动之后,能设置宽度的话就给元素加宽度.如果需要宽度是内容撑开,就给它里边的块元素加上浮动: 解决方案:给需要宽度由内容撑开的元素加上浮动 css样式: <style> .box{ ...