目錄:
Dapper源碼學習和源碼修改(上篇主要講解入參解析)
Dapper源碼學習和源碼修改(下篇主要講解出參解析)
繼上篇講了下自己學習Dapper的心得之后,下篇也隨之而來,上篇主要講的入參解析那下篇自然主打出參映射了。
好了,廢話不多說,開始吧。
學習之前你的先學習怎么使用Dapper,這個我在上篇都提過,如果沒使用過Dapper的同學,先去看看怎么使用吧,我這也簡單貼一部分代碼吧。
使用查詢的Demo
//查詢
sql = "select * from Teacher";
var list = SqlMapper.Query<Teacher>(conn, sql, null).ToList(); sql = "select * from Teacher left join Student on Teacher.Id=Student.Tid"; //一對一 var list1 = SqlMapper.Query<Teacher, Student, Teacher>(conn, sql, (t, s) => { if (t.Student == null) t.Student = new List<Student>(); t.Student.Add(s); return t; } , null, true, null, "Id", null, null); //一對多 Dictionary<string, Teacher> list2Dict = new Dictionary<string, Teacher>();//這個才是最后的結果 var list2 = SqlMapper.Query<Teacher, Student, Teacher>(conn, sql, (t, s) => { Teacher temp; if (!list2Dict.TryGetValue(t.Id, out temp)) { temp = t; list2Dict.Add(temp.Id, temp); } if (temp.Student == null) temp.Student = new List<Student>(); temp.Student.Add(s); return temp; } , null, true, null, "Id", null, null);
好了,我也不解釋,自己體會。
我們先看看Dapper提供了哪些對外的查詢方法呢,既然是將出參,只有查詢才會涉及到DataReader轉實體的呢,所以主要就看那幾個查詢方法就行了。
前兩個是一個實體映射的,后面三個是多個實體映射,正常情況下一個實體對應一個表,你也可以一個表對應多個實體也是可行的。
由淺入深,先看單個實體的查詢。
private static IEnumerable<T> QueryInternal<T>(IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType)
{
var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null); var info = GetCacheInfo(identity); using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType)) { using (var reader = cmd.ExecuteReader()) { Func<Func<IDataReader, object>> cacheDeserializer = delegate() { info.Deserializer = GetDeserializer(typeof(T), reader, 0, -1, false); SetQueryCache(identity, info); return info.Deserializer; }; if (info.Deserializer == null) { cacheDeserializer(); } var deserializer = info.Deserializer; while (reader.Read()) { object next; try { next = deserializer(reader); } catch (DataException) { deserializer = cacheDeserializer(); next = deserializer(reader); } yield return (T)next; } } } }
這個就是單個實體的查詢方法,用一張圖片說明
很明顯在讀取reader的時候 next = deserializer(reader); 就是這個將reader轉成實體的,那這個deserializer是什么呢,往上看啊,上面重點二字的地方就是創建deserializer 委托的地方,對了這里插一句這里委托Func(有返回值的泛型委托),之前在入參講解的時候那里委托是Action(無返回值的泛型委托)。
也就是說deserializer就是創建委托的地方,我們去看看它的廬山真面目。
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
Func<IDataReader, object> func = null; if (IsSimpleValue(type)) { func = GetSimpleDeserializer(type, startBound); } else if (typeof(IDictionary).IsAssignableFrom(type)) { func = GetDictionaryDeserializer(type, startBound); } else if (type.IsClass) { func = GetClassDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); } return func; }
func = GetSimpleDeserializer(type, startBound); func = GetDictionaryDeserializer(type, startBound); 這兩個是我擴展的兩種類型,就是為了讓出參支持簡單類型和繼承IDictionary的類型。
而 func = GetClassDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); 這個才是重點中的難點,這個就是將reader轉成實體的委托。
private static Func<IDataReader, object> GetClassDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), type, new[] { typeof(IDataReader) }, true); var il = dm.GetILGenerator(); il.DeclareLocal(typeof(int)); il.DeclareLocal(type); bool haveEnumLocal = false; il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc_0); List<PropertyInfo> properties = GetSettableProps(type); List<FieldInfo> fields = GetSettableFields(type); if (length == -1) { length = reader.FieldCount - startBound; } if (reader.FieldCount <= startBound) { throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); } var names = new List<string>(); for (int i = startBound; i < startBound + length; i++) { names.Add(reader.GetName(i)); } var setters = new List<DynamicSetter>(); foreach (var name in names) { PropertyInfo p = FirstOrDefault(properties, new Func<PropertyInfo, bool>(delegate(PropertyInfo pro) { return Equals(pro.Name, name, StringComparison.Ordinal); })) ?? FirstOrDefault(properties, new Func<PropertyInfo, bool>(delegate(PropertyInfo pro) { return Equals(pro.Name, name, StringComparison.OrdinalIgnoreCase); })); FieldInfo f = FirstOrDefault(fields, new Func<FieldInfo, bool>(delegate(FieldInfo fin) { return Equals(fin.Name, name, StringComparison.Ordinal); })) ?? FirstOrDefault(fields, new Func<FieldInfo, bool>(delegate(FieldInfo fin) { return Equals(fin.Name, name, StringComparison.OrdinalIgnoreCase); })); setters.Add(new DynamicSetter { Name = name, Property = p, Field = f }); } int index = startBound; il.BeginExceptionBlock(); // stack is empty il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)); // stack is now [target] bool first = true; var allDone = il.DefineLabel(); foreach (var item in setters) { if (item.Property != null || item.Field != null) { il.Emit(OpCodes.Dup); // stack is now [target][target] Label isDbNullLabel = il.DefineLabel(); Label finishLabel = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader] EmitInt32(il, index); // stack is now [target][target][reader][index] il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index] il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] Type memberType = item.Property != null ? item.Property.PropertyType : item.Field.FieldType; if (memberType == typeof(char) || memberType == typeof(char?)) { il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value] } else { il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null] il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object] // unbox nullable enums as the primitive, i.e. byte etc var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType; if (unboxType.IsEnum) { if (!haveEnumLocal) { il.DeclareLocal(typeof(string)); haveEnumLocal = true; } Label isNotString = il.DefineLabel(); il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null] il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null] il.Emit(OpCodes.Stloc_2); // stack is now [target][target][value-as-object][string or null] il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object] il.Emit(OpCodes.Pop); // stack is now [target][target] il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token] il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type] il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string] il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true] il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object] il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] if (nullUnderlyingType != null) { il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); } if (item.Property != null) { var setter = item.Property.DeclaringType == type ? item.Property.GetSetMethod(true) : item.Property.DeclaringType.GetProperty(item.Property.Name).GetSetMethod(true); il.Emit(OpCodes.Callvirt, setter); // stack is now [target] } else { il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] } il.Emit(OpCodes.Br_S, finishLabel); il.MarkLabel(isNotString); } //if (memberType == typeof(System.Data.Linq.Binary)) //{ // il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array] // il.Emit(OpCodes.Newobj, typeof(System.Data.Linq.Binary).GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary] //} //else //{ il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] //} if (nullUnderlyingType != null && nullUnderlyingType.IsEnum) { il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); } } if (item.Property != null) { var setter = item.Property.DeclaringType == type ? item.Property.GetSetMethod(true) : item.Property.DeclaringType.GetProperty(item.Property.Name).GetSetMethod(true); il.Emit(OpCodes.Callvirt, setter); // stack is now [target] } else { il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] } il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value] il.Emit(OpCodes.Pop); // stack is now [target][target] il.Emit(OpCodes.Pop); // stack is now [target] if (first && returnNullIfFirstMissing) { il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldnull); // stack is now [null] il.Emit(OpCodes.Stloc_1); il.Emit(OpCodes.Br, allDone); } il.MarkLabel(finishLabel); } first = false; index += 1; } il.Emit(OpCodes.Stloc_1); // stack is empty il.MarkLabel(allDone); il.BeginCatchBlock(typeof(Exception)); // stack is Exception il.Emit(OpCodes.Ldloc_0); // stack is Exception, index il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Stloc_1); // to make it verifiable il.EndExceptionBlock(); il.Emit(OpCodes.Ldloc_1); // stack is empty il.Emit(OpCodes.Ret); return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>)); }
呵呵,我也不多說,這個就是Dapper核心價值所在,使用Emit創建實體。
上面不是說到,我也擴展出參支持兩種類型嘛,使用的時候就是這樣
其一:IDictionary
sql = "select * from student";// where Id='916a84c6-85cb-4b41-b52a-96a0685d91b5'"; var sex = SqlMapper.Query<Dictionary<string, object>>(conn, sql, null).ToList();
很簡單的將結果轉成 Dictionary ,它的委托實現如下:
private static Func<IDataReader, object> GetDictionaryDeserializer(Type type, int index)
{
return delegate(IDataReader r) { IDictionary ht = Activator.CreateInstance(type) as IDictionary; for (int i = 0; i < r.FieldCount; i++) { ht.Add(r.GetName(i), r[i]); } return ht; }; }
其二:SimpleValue
何為SimpleValue呢,看下面
private static bool IsSimpleValue(Type type)
{
if ( type.IsEnum || isSame(type, typeof(byte)) || isSame(type, typeof(byte?)) || isSame(type, typeof(sbyte)) || isSame(type, typeof(sbyte?)) || isSame(type, typeof(long)) || isSame(type, typeof(long?)) || isSame(type, typeof(ulong)) || isSame(type, typeof(ulong?)) || isSame(type, typeof(short)) || isSame(type, typeof(short?)) || isSame(type, typeof(ushort)) || isSame(type, typeof(ushort?)) || isSame(type, typeof(int)) || isSame(type, typeof(int?)) || isSame(type, typeof(uint)) || isSame(type, typeof(uint?)) || isSame(type, typeof(float)) || isSame(type, typeof(float?)) || isSame(type, typeof(double)) || isSame(type, typeof(double?)) || isSame(type, typeof(decimal)) || isSame(type, typeof(decimal?)) || isSame(type, typeof(char)) || isSame(type, typeof(char?)) || isSame(type, typeof(bool)) || isSame(type, typeof(bool?)) || isSame(type, typeof(DateTime)) || isSame(type, typeof(DateTime?)) || isSame(type, typeof(string)) || isSame(type, typeof(object)) ) return true; else return false; }
如果是上面的類型,我就會用下面的委托來轉換reader
private static Func<IDataReader, object> GetSimpleDeserializer(Type type, int index)
{
return delegate(IDataReader r) { object obj = r.GetValue(index); if (obj == null || (obj is DBNull)) return type.IsValueType ? Activator.CreateInstance(type) : null; else { if (type.IsEnum) obj = Convert.ChangeType(obj, typeof(int)); else obj = Convert.ChangeType(obj, type); return obj; } }; }
后續,上面只是講到單個實體查詢的情況,至於多個實體的查詢,只需理解兩個參數
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, bool buffered, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType)
這兩個 splitOn 和 map 如果理解了這兩個參數,其他跟單個實體是一樣,至於這兩個參數我先不講解,后面我會提供源碼,自己看,或者以后抽空我再講講。
總結:
出參就講到這吧,后續我看講不講Dapper擴展的,會提供源碼下載,源碼可能和文章會有些出入,發布文章后我修改過源碼。
源碼下載: