深入Dapper.NET源码 (文长)
目录
- 前言、目录、安装环境
- Dynamic Query 原理 Part1
- Dynamic Query 原理 Part2
- Strongly Typed Mapping 原理 Part1 : ADO.NET对比Dapper
- Strongly Typed Mapping 原理 Part2 : Reflection版本
- Strongly Typed Mapping 原理 Part3 : 动态建立方法重要概念「结果反推程式码」优化效率
- Strongly Typed Mapping 原理 Part4 : Expression版本
- Strongly Typed Mapping 原理 Part5 : Emit IL反建立C#代码
- Strongly Typed Mapping 原理 Part6 : Emit版本
- Dapper 效率快关键之一 : Cache 缓存原理
- 错误SQL字串拼接方式,会导致效率慢、内存泄漏
- Dapper SQL正确字串拼接方式 : Literal Replacement
- Query Multi Mapping 使用方式
- Query Multi Mapping 底层原理
- QueryMultiple 底层原理
- TypeHandler 自订Mapping逻辑使用、底层逻辑
- CommandBehavior的细节处理
- Parameter 参数化底层原理
- IN 多集合参数化底层原理
- DynamicParameter 底层原理、自订实作
- 单次、多次 Execute 底层原理
- ExecuteScalar应用
- 总结
1.前言、目录、安装环境 Dapper Github 首页 Clone最新版本到自己本机端



个人环境
- 数据库 : MSSQLLocalDB
- Visaul Studio版本 : 2019
- LINQ Pad 5 版本
- Dapper版本 : V2.0.30
- 反编译 : ILSpy
2.Dynamic Query 原理 Part1 HtmlTableHelper就是利用这特性,自动将Dapper Dynamic Query转成Table Html,如以下代码跟图片
using (var cn = "Your Connection")
{
var sourceData = cn.Query(@"select 'ITWeiHan' Name,25 Age,'M' Gender");
var tablehtml = sourceData.ToHtmlTable(); //Result : Name Age Gender ITWeiHan 25 M
}
using (var cn = "Your Connection")
{
var sourceData = cn.Query(@"select 'ITWeiHan' Name,25 Age,'M' Gender");
var tablehtml = sourceData.ToHtmlTable(); //Result : Name Age Gender ITWeiHan 25 M
}
4. Strongly Typed Mapping 原理 Part1 : ADO.NET对比Dapper il-visualizer 开源工具,它可以在Runtime查看DynamicMethod生成的IL。
它预设支持vs 2015、2017,假如跟我一样使用vs2019的读者,需要注意
- 需要手动解压缩到
%USERPROFILE%\Documents\Visual Studio 2019
路径下面
.netstandard2.0
专案,需要建立netstandard2.0
并解压缩到该资料夹
最后重开visaul studio并debug运行,进到GetTypeDeserializerImpl方法,对DynamicMethod点击放大镜 > 選擇IL visualizer > 查看Runtime
生成的IL代码
可以得出以下IL
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: newobj Void .ctor()/Demo.User
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: dup
IL_000a: ldc.i4.0
IL_000b: stloc.0
IL_000c: ldarg.0
IL_000d: ldc.i4.0
IL_000e: callvirt System.Object get_Item(Int32)/System.Data.IDataRecord
IL_0013: dup
IL_0014: stloc.2
IL_0015: dup
IL_0016: isinst System.DBNull
IL_001b: brtrue.s IL_0029
IL_001d: unbox.any System.String
IL_0022: callvirt Void set_Name(System.String)/Demo.User
IL_0027: br.s IL_002b
IL_0029: pop
IL_002a: pop
IL_002b: dup
IL_002c: ldc.i4.1
IL_002d: stloc.0
IL_002e: ldarg.0
IL_002f: ldc.i4.1
IL_0030: callvirt System.Object get_Item(Int32)/System.Data.IDataRecord
IL_0035: dup
IL_0036: stloc.2
IL_0037: dup
IL_0038: isinst System.DBNull
IL_003d: brtrue.s IL_004b
IL_003f: unbox.any System.Int32
IL_0044: callvirt Void set_Age(Int32)/Demo.User
IL_0049: br.s IL_004d
IL_004b: pop
IL_004c: pop
IL_004d: stloc.1
IL_004e: leave IL_0060
IL_0053: ldloc.0
IL_0054: ldarg.0
IL_0055: ldloc.2
IL_0056: call Void ThrowDataException(System.Exception, Int32, System.Data.IDataReader, System.Object)/Dapper.SqlMapper
IL_005b: leave IL_0060
IL_0060: ldloc.1
IL_0061: ret
要了解这段IL之前需要先了解ADO.NET DataReader快速读取资料方式
会使用GetItem By Index
方式,如以下代码
public static class DemoExtension
{
private static User CastToUser(this IDataReader reader)
{
var user = new User();
var value = reader[0];
if(!(value is System.DBNull))
user.Name = (string)value;
var value = reader[1];
if(!(value is System.DBNull))
user.Age = (int)value;
return user;
}
public static IEnumerable Query(this IDbConnection cnn, string sql)
{
if (cnn.State == ConnectionState.Closed) cnn.Open();
using (var command = cnn.CreateCommand())
{
command.CommandText = sql;
using (var reader = command.ExecuteReader())
while (reader.Read())
yield return reader.CastToUser();
}
}
}
接着查看此Demo - CastToUser方法生成的IL代码
DemoExtension.CastToUser:
IL_0000: nop
IL_0001: newobj User..ctor
IL_0006: stloc.0 // user
IL_0007: ldarg.0
IL_0008: ldc.i4.0
IL_0009: callvirt System.Data.IDataRecord.get_Item
IL_000E: stloc.1 // value
IL_000F: ldloc.1 // value
IL_0010: isinst System.DBNull
IL_0015: ldnull
IL_0016: cgt.un
IL_0018: ldc.i4.0
IL_0019: ceq
IL_001B: stloc.2
IL_001C: ldloc.2
IL_001D: brfalse.s IL_002C
IL_001F: ldloc.0 // user
IL_0020: ldloc.1 // value
IL_0021: castclass System.String
IL_0026: callvirt User.set_Name
IL_002B: nop
IL_002C: ldarg.0
IL_002D: ldc.i4.1
IL_002E: callvirt System.Data.IDataRecord.get_Item
IL_0033: stloc.1 // value
IL_0034: ldloc.1 // value
IL_0035: isinst System.DBNull
IL_003A: ldnull
IL_003B: cgt.un
IL_003D: ldc.i4.0
IL_003E: ceq
IL_0040: stloc.3
IL_0041: ldloc.3
IL_0042: brfalse.s IL_0051
IL_0044: ldloc.0 // user
IL_0045: ldloc.1 // value
IL_0046: unbox.any System.Int32
IL_004B: callvirt User.set_Age
IL_0050: nop
IL_0051: ldloc.0 // user
IL_0052: stloc.s 04
IL_0054: br.s IL_0056
IL_0056: ldloc.s 04
IL_0058: ret
跟Dapper生成的IL比对可以发现大致是一样的
(差异部分后面会讲解),代表两者在运行的逻辑、效率上都会是差不多的,这也是为何Dapper效率接近原生ADO.NET
的原因之一。
5. Strongly Typed Mapping 原理 Part2 : Reflection版本 c# - What's faster: expression trees or manually emitting IL
话虽如此,但有一些厉害的开源专案就是使用Emit管理细节,如果想看懂它们,就需要基础的Emit IL概念
。
10.Dapper 效率快关键之一 : Cache 缓存原理 连结#1210)询问过作者,这边是回答 : 主要避免忽略错误,像是在DataReader提早关闭情况
QueryFirst搭配SingleRow,
有时候我们会遇到select top 1
知道只会读取一行资料的情况,这时候可以使用QueryFirst
。它使用CommandBehavior.SingleRow
可以避免浪费资源只读取一行资料。
另外可以发现此段除了while (reader.NextResult()){}
外还有while (reader.Read()) {}
,同样是避免忽略错误,这是一些公司自行土炮ORM会忽略的地方。
与QuerySingle之间的差别
两者差别在QuerySingle没有使用CommandBehavior.SingleRow,至于为何没有使用,是因为需要有多行资料才能判断是否不符合条件并抛出Exception告知使用者
。
这段有一个特别好玩小技巧可以学,错误处理直接沿用对应LINQ的Exception,举例:超过一行资料错误,使用new int[2].Single()
,这样不用另外维护Exceptiono类别,还可以拥有i18N多国语言化。
18.Parameter 参数化底层原理 c# - How to remove the last few segments of Emit IL at runtime - Stack Overflow
最后笔者想说 :
写这篇的初衷,是希望本系列可以帮助到读者
- 了解底层逻辑,知其所以然,避免写出吃掉效能的怪兽,更进一步完整的利用Dapper优点开发专案
- 可以轻松面对Dapper的面试,比起一般使用Dapper工程师回答出更深层的概念
- 从最简单Reflection到常用Expression到最细节Emit从头建立Mapping方法,带读者
渐进式
了解Dapper底层强型别Mapping逻辑 - 了解动态建立方法的重要概念
「结果反推程式码」
- 有基本IL能力,可以利用IL反推C#代码方式看懂其他专案的底层Emit逻辑
- 了解Dapper因为缓存的算法逻辑,所以
不能使用错误字串拼接SQL
感谢大家阅读到最后,假如喜欢本系列,欢迎留言、交流 ??