iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 4
3
Software Development

🌊 進階學習 ADO.NET、Dapper、Entity Framework 系列 第 4

【深入Dapper.NET源碼】 Strongly Typed Mapping 原理 Part1 : ADO.NET對比Dapper

接下來是Dapper關鍵功能 Strongly Typed Mapping,因為難度高,這邊會切分成多篇來解說。

第一篇先以ADO.NET DataReader GetItem By Index跟Dapper Strongly Typed Query對比,查看兩者IL的差異,了解Dapper Query Mapping的主要邏輯。

有了邏輯後,如何實作,我這邊依序用三個技術 :Reflection、Expression、Emit 從頭實作三個版本Query方法來讓讀者漸進式了解。


ADO.NET對比Dapper

首先使用以下代碼來追蹤Dapper Query邏輯

class Program
{
	static void Main(string[] args)
	{
		using (var cn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDB;Integrated Security=SSPI;Initial Catalog=master;"))
		{
			var result = cn.Query<User>("select N'暐翰' Name , 25 Age").First();
			Console.WriteLine(result.Name);
			Console.WriteLine(result.Age);
		}
	}
}

public class User
{
	public string Name { get; set; }
	public int Age { get; set; }
}

這邊需要重點來看Dapper.SqlMapper.GenerateDeserializerFromMap方法,它負責Mapping的邏輯,可以看到裡面大量使用Emit IL技術。

20191004012713.png

要了解這段IL邏輯,我的方式 :「不應該直接進到細節,而是先查看完整生成的IL」,至於如何查看,這邊需要先準備 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代碼
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<User> Query<T>(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的原因之一。


上一篇
【深入Dapper.NET源碼】Dynamic Query 原理 Part2
下一篇
【深入Dapper.NET源碼】 Strongly Typed Mapping 原理 Part2 : Reflection版本
系列文
🌊 進階學習 ADO.NET、Dapper、Entity Framework 30

尚未有邦友留言

立即登入留言