Github Link : https://github.com/shps951023/Trace-Dapper.NET-Source-Code
4. Strongly Typed Mapping Part1: ADO.NET vs. Dapper
Next is the key function of Dapper, Strongly Typed Mapping
. Because of the difficulty, it will be divided into multiple parts for explanation.
In the first part, compare ADO.NET DataReader GetItem By Index with Dapper Strongly Typed Query, check the difference between the IL and understand the main logic of Dapper Query Mapping.
With the logic, how to implement it, I use three techniques in order: Reflection、Expression、Emit
implement three versions of the Query method from scratch to let readers understand gradually.
ADO.NET vs. Dapper
First use the following code to trace the Dapper Query logic
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'Wei' 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; }
}
Here we need to focus on the Dapper.SqlMapper.GenerateDeserializerFromMap
method, it is responsible for the logic of Mapping, you can see that a large number of Emit IL technology is used inside.
To understand this IL logic, my way: "You should not go directly to the details, but check the complete IL first"
As for how to view it, you need to prepare the il-visualizer open source tool first, which can view the IL generated by DynamicMethod at Runtime.
It supports vs 2015 and 2017 by default. If you use vs2019 like me
- Need to manually extract the
%USERPROFILE%\Documents\Visual Studio 2019
path below -
.netstandard2.0
The project needs to be creatednetstandard2.0
and unzipped to this folder
Finally reopen visaul studio and run debug, enter the GetTypeDeserializerImpl method, click the magnifying glass> IL visualizer> view the Runtime
generated IL code for the DynamicMethod
The following IL can be obtained
IL_0000 : LDC . i4 .0
IL_0001 : stloc .0
IL_0002 : newobj Void . ctor () / Demo . Customer
IL_0007 : stloc .1
IL_0008 : ldloc .1
IL_0009 : after
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
To understand this IL, you need to understand how it ADO.NET DataReader fast way to read data
will be used GetItem By Index
, such as the following code
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();
}
}
}
Then look at the IL code generated by this Demo-CastToUser method
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
It can be compared with the IL generated by Dapper shows that it is roughly the same
(the differences will be explained later), which means that the logic and efficiency of the two operations will be similar, which is Dapper efficiency is close to the native ado.net
the reasons why.
5. Strongly Typed Mapping Part2: Reflection version
In the previous ado.net Mapping example, we found a serious problem with there is no way to share multiple classes of methods, and each new class requires a code rewrite
. To solve this problem, write a common method that does different logical processing for different classes during the Runtime.
There are three main implementation methods: Reflection, Expression, and Emit. Here, I will first introduce the simplest method: "Reflection". I will use reflection to simulate Query to write code from scratch to give readers a preliminary understanding of dynamic processing concepts. (If experienced readers can skip this article)
Logic:
- Use generics to pass dynamic class
- Use
Generic constraints new()
to create objects dynamically - DataReader need to use
attribute string name is used as Key
, you can use Reflection to get the attribute name of the dynamic type and get the database data through theDataReader this[string parameter]
- Use PropertyInfo.SetValue to dynamically assign database data to objects
Finally got the following code:
public static class DemoExtension
{
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql) where T : new()
{
using (var command = cnn.CreateCommand())
{
command.CommandText = sql;
using (var reader = command.ExecuteReader())
while (reader.Read())
yield return reader.CastToType<T>();
}
}
// 1. Use generics to pass dynamic class
private static T CastToType < T >( this IDataReader reader ) where T : new ()
{
// 2. Use `Generic constraints new()` to create objects dynamically
var instance = new T ();
// 3.DataReader need to use `attribute string name is used as Key` , you can use Reflection to get the attribute name of the dynamic type and get the database data through the `DataReader this[string parameter]`
var type = typeof ( T );
var props = type . GetProperties ();
foreach ( var p in props )
{
var val = reader[p.Name];
// 4. Use PropertyInfo.SetValue to dynamically assign database data to objects
if ( ! ( Val is System . DBNull ))
p . SetValue ( instance , val );
}
return instance;
}
}
The advantage of the Reflection version is that the code is simple
, but it has the following problems
The attribute query should not be repeated, and it should be ignored if it is not used. Example: If the class has N properties, SQL means to query 3 fields, and the ORM PropertyInfo foreach N times is not 3 times each time. And Dapper specially optimized this logic in Emit IL:
「Check how much you use, not waste」
.
Efficiency issues:
The reflection efficiency will be slower. the solution will be introduced later:
「Key Cache + Dynamic Create Method」
exchange space for time.Using the string Key value will call more
GetOrdinal
methods, you can check the official MSDN explanationits efficiency is worse than Index value
.
Top comments (0)