感覺好久沒有寫博客了, 這幾天有點小忙, 接下來會更忙, 索性就先寫一篇吧. 後面估計會有更長的一段時間不會更新博客了.
廢話不多說, 先上菜.
一、示例
1. 先建類, 類的名稱與讀取的表名並沒有什麼關系,可以不一樣, 然後就是其中的屬性大小寫不限
public class Tch_Teacher
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDoublePosition { get; set; }
public DateTime CreateDate { get; set; }
}
public class Test : Tch_Teacher //, ISupportInitialize 此接口有兩個方法, BeginInit, EndInit
{
//[ExplicitConstructor] Dapper會優先查找設置了此屬性的構造函數
//public Test() { }
public string BId { get; set; }
//public void BeginInit()
//{
// Console.WriteLine("Test BeginInit");
//}
//public void EndInit()
//{
// Console.WriteLine("Test EndInit");
//}
}
2. 測試代碼
class Program
{
static void Main(string[] args)
{
var conStr = ConfigurationManager.ConnectionStrings["ConStr"].ToString();
using (IDbConnection conn = new MySqlConnection(conStr))
{
var sql = "select Count(1) from tch_teacher where id>@Id limit 3;";
//Console.WriteLine(conn.Query<int?>(sql, new { Id = 10 })); //error
Console.WriteLine(conn.Query<int>(sql, new { Id = 10 }).FirstOrDefault()); // 19
Console.WriteLine(conn.Query<string>(sql, new { Id = 1 }).FirstOrDefault()); // 19
sql = "select Id, BId, No, Name, CreateDate from tch_teacher limit 3;"; //No這個字段, 在類中並沒有
var list = conn.Query<Test>(sql);
Console.WriteLine(list.ToList().FirstOrDefault().BId); // c5f5959e-0744-42cd-a843-145e28149d9b
}
Console.ReadKey();
}
}
接下來, 可以進入Dapper的部分了
三、Dapper 開始
Query<int/string> 和 Query<Test>在讀取數據的部分是一樣的, 開始出現不同的地方主要體現在 object to model 的部分,
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
首先注意一個地方
static SqlMapper()
{
//這部分是 簡單類型處理用到的, 當然這其中並不僅僅只有簡單類型
typeMap = new Dictionary<Type, DbType>();
typeMap[typeof(byte)] = DbType.Byte;
typeMap[typeof(sbyte)] = DbType.SByte;
typeMap[typeof(short)] = DbType.Int16;
typeMap[typeof(ushort)] = DbType.UInt16;
typeMap[typeof(int)] = DbType.Int32;
typeMap[typeof(uint)] = DbType.UInt32;
typeMap[typeof(long)] = DbType.Int64;
typeMap[typeof(ulong)] = DbType.UInt64;
typeMap[typeof(float)] = DbType.Single;
typeMap[typeof(double)] = DbType.Double;
typeMap[typeof(decimal)] = DbType.Decimal;
typeMap[typeof(bool)] = DbType.Boolean;
typeMap[typeof(string)] = DbType.String;
typeMap[typeof(char)] = DbType.StringFixedLength;
typeMap[typeof(Guid)] = DbType.Guid;
typeMap[typeof(DateTime)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
typeMap[typeof(TimeSpan)] = DbType.Time;
typeMap[typeof(byte[])] = DbType.Binary;
typeMap[typeof(byte?)] = DbType.Byte;
typeMap[typeof(sbyte?)] = DbType.SByte;
typeMap[typeof(short?)] = DbType.Int16;
typeMap[typeof(ushort?)] = DbType.UInt16;
typeMap[typeof(int?)] = DbType.Int32;
typeMap[typeof(uint?)] = DbType.UInt32;
typeMap[typeof(long?)] = DbType.Int64;
typeMap[typeof(ulong?)] = DbType.UInt64;
typeMap[typeof(float?)] = DbType.Single;
typeMap[typeof(double?)] = DbType.Double;
typeMap[typeof(decimal?)] = DbType.Decimal;
typeMap[typeof(bool?)] = DbType.Boolean;
typeMap[typeof(char?)] = DbType.StringFixedLength;
typeMap[typeof(Guid?)] = DbType.Guid;
typeMap[typeof(DateTime?)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
typeMap[typeof(TimeSpan?)] = DbType.Time;
typeMap[typeof(object)] = DbType.Object;
//這個方法可以實現自定義處理, 它是一個public static 方法
AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), false);
}
然後看GetDeserializer方法
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
// dynamic is passed in as Object ... by c# design
if (type == typeof(object)
|| type == typeof(DapperRow))
{
//object / dynamic 類型, 會執行以下方法
return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing);
}
Type underlyingType = null;
if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary ||
(type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum)))
{
ITypeHandler handler;
if (typeHandlers.TryGetValue(type, out handler))
{
//自定義處理
return GetHandlerDeserializer(handler, type, startBound);
}
//復雜類型的處理
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
}
//以上簡單類型, 值類型, 可空值類型, 枚舉, linq的二進制 的處理
return GetStructDeserializer(type, underlyingType ?? type, startBound);
}
這裡我只介紹 復雜類型的處理方式了, 至於其他的, 跟Emit的主題關系不是很大, 有興趣的童鞋, 可以自己去看一下, 應該是能看懂的
由於 GetTypeDeserializer 這個方法實在是太長了, 我把說明都寫在注釋裡面去吧. 按照我的注釋, 應該是能看懂整個過程的. 可能還是IL那一段不太好懂, 我第一次看的時候, 就看到那裡就沒繼續看下去了, 實在是不想繼續看了. 以下是代碼部分
1 /// <summary>
2 /// Internal use only
3 /// </summary>
4 /// <param name="type"></param>
5 /// <param name="reader"></param>
6 /// <param name="startBound"></param>
7 /// <param name="length"></param>
8 /// <param name="returnNullIfFirstMissing"></param>
9 /// <returns></returns>
10 public static Func<IDataReader, object> GetTypeDeserializer(
11 #if CSHARP30
12 Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
13 #else
14 Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
15 #endif
16 )
17 {
18 //創建動態方法 Deserialize[Guid]
19 var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true);
20 var il = dm.GetILGenerator();
21 il.DeclareLocal(typeof(int)); //定義本地變量 loc0
22 il.DeclareLocal(type); //定義本地變量 loc1 -> target
23 il.Emit(OpCodes.Ldc_I4_0);
24 il.Emit(OpCodes.Stloc_0); //初始化本地變量loc0, loc0 = 0
25
26 if (length == -1)
27 {
28 length = reader.FieldCount - startBound; //獲取要轉換字段的個數
29 }
30
31 if (reader.FieldCount <= startBound)
32 {
33 throw MultiMapException(reader);
34 }
35
36 //獲取讀取出來的字段名, 並轉入數組中 -> string[] Id, BId, No, Name, CreateDate
37 var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray();
38
39 ITypeMap typeMap = GetTypeMap(type); //new DefaultTypeMap(type)
40
41 int index = startBound;
42
43 //有參構造函數
44 ConstructorInfo specializedConstructor = null;
45 //需要初始化標志
46 bool supportInitialize = false;
47 if (type.IsValueType) //target是值類型
48 {
49 il.Emit(OpCodes.Ldloca_S, (byte)1); //加載loc1的地址
50 il.Emit(OpCodes.Initobj, type); //初始化loc1, loc1 = 0
51 }
52 else //target是引用類型
53 {
54 var types = new Type[length];
55 for (int i = startBound; i < startBound + length; i++)
56 {
57 //獲取讀到的db值的類型
58 types[i - startBound] = reader.GetFieldType(i);
59 }
60 //查找標記了ExplicitConstructor屬性(Attribute)的構造函數
61 var explicitConstr = typeMap.FindExplicitConstructor();
62 if (explicitConstr != null)
63 {
64 #region 存在
65 var structLocals = new Dictionary<Type, LocalBuilder>();
66
67 var consPs = explicitConstr.GetParameters(); //獲取該構造函數上的參數集
68
69 #region 遍歷加載參數
70 foreach (var p in consPs)
71 {
72 //引用類型
73 if (!p.ParameterType.IsValueType)
74 {
75 //如果傳入參數為復雜類型, 則以 null 來處理
76 il.Emit(OpCodes.Ldnull);
77 }
78 else //值類型
79 {
80 LocalBuilder loc;
81 if (!structLocals.TryGetValue(p.ParameterType, out loc))
82 {
83 //定義本地變量
84 structLocals[p.ParameterType] = loc = il.DeclareLocal(p.ParameterType);
85 }
86
87 il.Emit(OpCodes.Ldloca, (short)loc.LocalIndex);
88 il.Emit(OpCodes.Initobj, p.ParameterType); //初始化傳入參數, a=0,b=false之類的
89 il.Emit(OpCodes.Ldloca, (short)loc.LocalIndex);
90 il.Emit(OpCodes.Ldobj, p.ParameterType); //加載初始化後的參數
91 }
92 }
93 #endregion
94
95 il.Emit(OpCodes.Newobj, explicitConstr); //創建對象 new target(...);
96 il.Emit(OpCodes.Stloc_1); //loc1 = target
97
98 //target 是否實現 ISupportInitialize 接口, 如果實現, 則調用其 BeginInit 方法
99 supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
100 if (supportInitialize)
101 {
102 il.Emit(OpCodes.Ldloc_1);
103 il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null);
104 }
105 #endregion
106 }
107 else
108 {
109 #region 不存在
110 var ctor = typeMap.FindConstructor(names, types); //查找構造函數, 優先返回無參構造函數
111 if (ctor == null)
112 {
113 //找不到能用的構造函數
114 string proposedTypes = "(" + string.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")";
115 throw new InvalidOperationException(string.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization", proposedTypes, type.FullName));
116 }
117
118 if (ctor.GetParameters().Length == 0)
119 {
120 il.Emit(OpCodes.Newobj, ctor);
121 il.Emit(OpCodes.Stloc_1); //loc1 = new target();
122 supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
123 if (supportInitialize)
124 {
125 il.Emit(OpCodes.Ldloc_1);
126 il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null);
127 }
128 }
129 else
130 {
131 specializedConstructor = ctor;
132 }
133 #endregion
134 }
135 }
136
137 //try 開始
138 il.BeginExceptionBlock();
139 if (type.IsValueType)
140 {
141 //如果是值類型, 加載target的地址
142 il.Emit(OpCodes.Ldloca_S, (byte)1);// [target]
143 }
144 else if (specializedConstructor == null) //構造函數為無參構造函數
145 {
146 //引用類型, 則直接使用變量即可
147 il.Emit(OpCodes.Ldloc_1);// [target]
148 }
149
150 //用reader中的列去匹配target中的屬性, 匹配不上, 則顯示為null, 此處的No為null
151 var members = (specializedConstructor != null
152 ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n))
153 : names.Select(n => typeMap.GetMember(n))).ToList(); //無參
154
155 // stack is now [target]
156
157 bool first = true;
158 var allDone = il.DefineLabel();
159 int enumDeclareLocal = -1,
160 //定義第二個本地變量,object類型的, 然後返回此本地變量的index值, 其實就是截止目前, 定義了本地變量的個數
161 valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex;
162 foreach (var item in members)
163 {
164 if (item != null)
165 {
166 #region object to model
167
168 if (specializedConstructor == null) //無參構造函數存在
169 il.Emit(OpCodes.Dup); // stack is now [target][target]
170
171 Label isDbNullLabel = il.DefineLabel();
172 Label finishLabel = il.DefineLabel();
173
174 il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
175 EmitInt32(il, index); // stack is now [target][target][reader][index]
176 il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
177 il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] //loc0 = [index]
178 //獲取reader讀取的值, reader[index]
179 il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
180 il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object]
181 StoreLocal(il, valueCopyLocal); //將 reader[index]的值, 存放到本地變量 loc_valueCopyLocal 中
182
183 Type colType = reader.GetFieldType(index); //reader[index] 的列的類型 source
184 Type memberType = item.MemberType; //target[item] 的類型 target
185
186 //如果目標類型為char 或者 char? , 則調用ReadChar / ReadNullableChar方法來完成轉換
187 if (memberType == typeof(char) || memberType == typeof(char?))
188 {
189 il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(
190 memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
191 }
192 else
193 {
194 il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object]
195 //判斷是否為DBNull類型, 如果是, 則跳轉到 標簽isDbNullLabel
196 il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
197 il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object]
198
199 // unbox nullable enums as the primitive, i.e. byte etc
200 // int? -> int, int/string -> null
201 var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
202 var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType;
203
204 if (unboxType.IsEnum)
205 {
206 Type numericType = Enum.GetUnderlyingType(unboxType);
207 if (colType == typeof(string))
208 {
209 if (enumDeclareLocal == -1)
210 {
211 enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
212 }
213 il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string]
214 StoreLocal(il, enumDeclareLocal); // stack is now [target][target]
215 il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
216 il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
217 LoadLocal(il, enumDeclareLocal); // stack is now [target][target][enum-type][string]
218 il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
219 il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
220 il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
221 }
222 else
223 {
224 FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType);
225 }
226
227 if (nullUnderlyingType != null)
228 {
229 il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
230 }
231 }
232 else if (memberType.FullName == LinqBinary)
233 {
234 il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
235 il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
236 }
237 else
238 {
239 TypeCode dataTypeCode = Type.GetTypeCode(colType),
240 unboxTypeCode = Type.GetTypeCode(unboxType);
241 bool hasTypeHandler;
242 if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
243 {
244 //判斷是否有自定義的轉換方法, 如果有, 則調用自定義的方法完成轉換
245 if (hasTypeHandler)
246 {
247 #pragma warning disable 618
248 il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod("Parse"), null); // stack is now [target][target][typed-value]
249 #pragma warning restore 618
250 }
251 else
252 {
253 //將指令中指定類型的已裝箱的表示形式轉換成未裝箱形式
254 il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
255 }
256 }
257 else
258 {
259 // not a direct match; need to tweak the unbox
260 FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null);
261 if (nullUnderlyingType != null)
262 {
263 il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
264 }
265 }
266 }
267 }
268 if (specializedConstructor == null)
269 {
270 // Store the value in the property/field
271 if (item.Property != null)
272 {
273 if (type.IsValueType)
274 {
275 il.Emit(OpCodes.Call, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
276 }
277 else
278 {
279 il.Emit(OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
280 }
281 }
282 else
283 {
284 il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
285 }
286 }
287
288 il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]
289
290 il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
291 if (specializedConstructor != null)
292 {
293 il.Emit(OpCodes.Pop);
294 if (item.MemberType.IsValueType)
295 {
296 int localIndex = il.DeclareLocal(item.MemberType).LocalIndex;
297 LoadLocalAddress(il, localIndex);
298 il.Emit(OpCodes.Initobj, item.MemberType);
299 LoadLocal(il, localIndex);
300 }
301 else
302 {
303 il.Emit(OpCodes.Ldnull);
304 }
305 }
306 else
307 {
308 il.Emit(OpCodes.Pop); // stack is now [target][target]
309 il.Emit(OpCodes.Pop); // stack is now [target]
310 }
311
312 if (first && returnNullIfFirstMissing)
313 {
314 il.Emit(OpCodes.Pop);
315 il.Emit(OpCodes.Ldnull); // stack is now [null]
316 il.Emit(OpCodes.Stloc_1);
317 il.Emit(OpCodes.Br, allDone);
318 }
319
320 il.MarkLabel(finishLabel);
321 #endregion
322 }
323
324 first = false;
325 index += 1;
326 }
327 if (type.IsValueType)
328 {
329 il.Emit(OpCodes.Pop);
330 }
331 else
332 {
333 //構造函數為有參的構造函數
334 if (specializedConstructor != null)
335 {
336 //創建對象
337 il.Emit(OpCodes.Newobj, specializedConstructor);
338 }
339 il.Emit(OpCodes.Stloc_1); // stack is empty
340
341 //實現 ISupportInitialize 接口, 調用 EndInit 方法, 完成初始化
342 if (supportInitialize)
343 {
344 il.Emit(OpCodes.Ldloc_1);
345 il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("EndInit"), null);
346 }
347 }
348 il.MarkLabel(allDone);
349 //try 結束 -> catch 開始
350 il.BeginCatchBlock(typeof(Exception)); // stack is Exception
351 il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
352 il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
353 LoadLocal(il, valueCopyLocal); // stack is Exception, index, reader, value
354 il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null); //拋出異常
355 il.EndExceptionBlock();
356 //catch 結束
357
358 il.Emit(OpCodes.Ldloc_1); // stack is [rval] 此處就是轉換後的最終結果
359 if (type.IsValueType)
360 {
361 il.Emit(OpCodes.Box, type);
362 }
363 il.Emit(OpCodes.Ret);
364
365 return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
366 }
其中的value-as-object是從reader中讀取出來的未轉換的數據, typed-value是轉換後的數據
本想做成可收縮的, 但是那種展開後, 不能點, 只要一點, 就自動收起來了, 感覺不方便, 所以還是貼出來了
其中還有些地方不夠詳細, 不過對照著這個, 去看Dapper源碼, 是可以看的懂了
在下一篇中, 我會畫出堆棧中的變化, 來減少理解難度