深入Dapper.NET源码 (文长)


目录

  1. 前言、目录、安装环境
  2. Dynamic Query 原理 Part1
  3. Dynamic Query 原理 Part2
  4. Strongly Typed Mapping 原理 Part1 : ADO.NET对比Dapper
  5. Strongly Typed Mapping 原理 Part2 : Reflection版本
  6. Strongly Typed Mapping 原理 Part3 : 动态建立方法重要概念「结果反推程式码」优化效率
  7. Strongly Typed Mapping 原理 Part4 : Expression版本
  8. Strongly Typed Mapping 原理 Part5 : Emit IL反建立C#代码
  9. Strongly Typed Mapping 原理 Part6 : Emit版本
  10. Dapper 效率快关键之一 : Cache 缓存原理
  11. 错误SQL字串拼接方式,会导致效率慢、内存泄漏
  12. Dapper SQL正确字串拼接方式 : Literal Replacement
  13. Query Multi Mapping 使用方式
  14. Query Multi Mapping 底层原理
  15. QueryMultiple 底层原理
  16. TypeHandler 自订Mapping逻辑使用、底层逻辑
  17. CommandBehavior的细节处理
  18. Parameter 参数化底层原理
  19. IN 多集合参数化底层原理
  20. DynamicParameter 底层原理、自订实作
  21. 单次、多次 Execute 底层原理
  22. ExecuteScalar应用
  23. 总结

 
 

1.前言、目录、安装环境 Dapper Github 首页 Clone最新版本到自己本机端
  • 建立.NET Core Console专案
    20191003173131.png
  • 需要安装NuGet SqlClient套件、添加Dapper Project Reference
    20191003173438.png
  • 下中断点运行就可以Runtime查看逻辑
    20191003215021.png
  • 个人环境

    • 数据库 : 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 : 
    NameAgeGender
    ITWeiHan25M
    }

    20191003212846.png

     
     

    4. Strongly Typed Mapping 原理 Part1 : ADO.NET对比Dapper il-visualizer 开源工具,它可以在Runtime查看DynamicMethod生成的IL。

    它预设支持vs 2015、2017,假如跟我一样使用vs2019的读者,需要注意

    1. 需要手动解压缩到
      %USERPROFILE%\Documents\Visual Studio 2019路径下面
      20191004005622.png
    2. .netstandard2.0专案,需要建立netstandard2.0并解压缩到该资料夹
      20191003044307.png

    最后重开visaul studio并debug运行,进到GetTypeDeserializerImpl方法,对DynamicMethod点击放大镜 > 選擇IL visualizer > 查看Runtime生成的IL代码
    20191010120715.png
    20191003044320.png

    可以得出以下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

    20190927163441.png

    话虽如此,但有一些厉害的开源专案就是使用Emit管理细节,如果想看懂它们,就需要基础的Emit IL概念

     
     

    10.Dapper 效率快关键之一 : Cache 缓存原理 连结#1210)询问过作者,这边是回答 : 主要避免忽略错误,像是在DataReader提早关闭情况


    QueryFirst搭配SingleRow,

    有时候我们会遇到select top 1知道只会读取一行资料的情况,这时候可以使用QueryFirst。它使用CommandBehavior.SingleRow可以避免浪费资源只读取一行资料。

    另外可以发现此段除了while (reader.NextResult()){}外还有while (reader.Read()) {},同样是避免忽略错误,这是一些公司自行土炮ORM会忽略的地方。
    20191003024206.png

    与QuerySingle之间的差别

    两者差别在QuerySingle没有使用CommandBehavior.SingleRow,至于为何没有使用,是因为需要有多行资料才能判断是否不符合条件并抛出Exception告知使用者

    这段有一个特别好玩小技巧可以学,错误处理直接沿用对应LINQ的Exception,举例:超过一行资料错误,使用new int[2].Single(),这样不用另外维护Exceptiono类别,还可以拥有i18N多国语言化。
    20191003025631.png
    20191003025334.png

     
     

    18.Parameter 参数化底层原理 c# - How to remove the last few segments of Emit IL at runtime - Stack Overflow
    https://ithelp.ithome.com.tw/upload/images/20190925/201059884hfaioQATW.png

    最后笔者想说 :
    写这篇的初衷,是希望本系列可以帮助到读者

    1. 了解底层逻辑,知其所以然,避免写出吃掉效能的怪兽,更进一步完整的利用Dapper优点开发专案
    2. 可以轻松面对Dapper的面试,比起一般使用Dapper工程师回答出更深层的概念
    3. 从最简单Reflection到常用Expression到最细节Emit从头建立Mapping方法,带读者渐进式了解Dapper底层强型别Mapping逻辑
    4. 了解动态建立方法的重要概念「结果反推程式码」
    5. 有基本IL能力,可以利用IL反推C#代码方式看懂其他专案的底层Emit逻辑
    6. 了解Dapper因为缓存的算法逻辑,所以不能使用错误字串拼接SQL

    感谢大家阅读到最后,假如喜欢本系列,欢迎留言、交流 ??