感覺好久沒有寫博客了, 這幾天有點小忙, 接下來會更忙, 索性就先寫一篇吧. 後面估計會有更長的一段時間不會更新博客了.
廢話不多說, 先上菜.
一、示例
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源碼, 是可以看的懂了
在下一篇中, 我會畫出堆棧中的變化, 來減少理解難度