出错条件:
1.实体属性为bool?类型
2.对应字段为可空的tinyint(1)类型
3.该字段查询结果内即含有null,又含有正常值
google答案,两种建议:
1.修改sql语句,直接cast转换(未通过)
2.修改字段类型为tinyint长度为2或更长(此法可行,测试发现,改成bit类型也行),在datareader 的getfieldtype时,tinyint长度为1类型为Boolean,大于1时类型为byte,short,int
然,因我的查询结果中此类字段数量较多,且以上两种方案并不满意,也不符合我的需求,故想自己另觅答案:
首先,想到修改源码,为开源做贡献。发现它是长这样的:
class="code_img_closed" src="/Upload/Images/2017120616/0015B68B3C38AA5B.gif" alt="">private static Func<IDataReader, object> GetTypeDeserializerImpl( Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) { var returnType = type.IsValueType() ? typeof(object) : type; var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(IDataReader) }, type, true); var il = dm.GetILGenerator(); il.DeclareLocal(typeof(int)); il.DeclareLocal(type); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc_0); if (length == -1) { length = reader.FieldCount - startBound; } if (reader.FieldCount <= startBound) { throw MultiMapException(reader); } //数据库字段名 var fieldNames = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray(); //ITypeMap 含type的字段,属性 ITypeMap typeMap = GetTypeMap(type); int index = startBound; ConstructorInfo specializedConstructor = null; #if !NETSTANDARD1_3 bool supportInitialize = false; #endif Dictionary<Type, LocalBuilder> structLocals = null; if (type.IsValueType()) { il.Emit(OpCodes.Ldloca_S, (byte)1); il.Emit(OpCodes.Initobj, type); } else { //字段映射,开始 var types = new Type[length]; for (int i = startBound; i < startBound + length; i++) { types[i - startBound] = reader.GetFieldType(i); } //获取ExplicitConstructor特性的构造函数 var explicitConstr = typeMap.FindExplicitConstructor(); if (explicitConstr != null) { var consPs = explicitConstr.GetParameters(); foreach (var p in consPs) { if (!p.ParameterType.IsValueType()) { il.Emit(OpCodes.Ldnull); } else { GetTempLocal(il, ref structLocals, p.ParameterType, true); } } il.Emit(OpCodes.Newobj, explicitConstr); il.Emit(OpCodes.Stloc_1); #if !NETSTANDARD1_3 supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); if (supportInitialize) { il.Emit(OpCodes.Ldloc_1); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); } #endif } else { //按构造函数参数个数倒序排列,查找合适的构造函数 var ctor = typeMap.FindConstructor(fieldNames, types); if (ctor == null) { string proposedTypes = "(" + string.Join(", ", types.Select((t, i) => t.FullName + " " + fieldNames[i]).ToArray()) + ")"; throw new InvalidOperationException($"A parameterless default constructor or one matching signature {proposedTypes} is required for {type.FullName} materialization"); } //无参构造 if (ctor.GetParameters().Length == 0) { //用指定构造函数创建一个新实例,赋值给类型变量 il.Emit(OpCodes.Newobj, ctor); il.Emit(OpCodes.Stloc_1); #if !NETSTANDARD1_3 supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); if (supportInitialize) { //取type实例 il.Emit(OpCodes.Ldloc_1); //调用公共方法 il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); } #endif } else { specializedConstructor = ctor; } } } //try块开始 il.BeginExceptionBlock(); //值类型 if (type.IsValueType()) { il.Emit(OpCodes.Ldloca_S, (byte)1);// [target] } else if (specializedConstructor == null) { il.Emit(OpCodes.Ldloc_1);// [target] } //根据数据库字段名,查找传入类型中对应的属性Property,若没有则查找字段Field var members = IsValueTuple(type) ? GetValueTupleMembers(type, fieldNames) : ((specializedConstructor != null ? fieldNames.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n)) : fieldNames.Select(n => typeMap.GetMember(n))).ToList()); // stack is now [target] bool first = true; var allDone = il.DefineLabel(); int enumDeclareLocal = -1, valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex; bool applyNullSetting = Settings.ApplyNullValues; foreach (var item in members) { if (item != null) { if (specializedConstructor == null) il.Emit(OpCodes.Dup); // stack is now [target][target] Label isDbNullLabel = il.DefineLabel(); Label finishLabel = il.DefineLabel(); //索引为0的参数即reader 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] il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object] StoreLocal(il, valueCopyLocal); //字段类型 Type colType = reader.GetFieldType(index);//tinyint(1) null //属性/字段类型 Type memberType = item.MemberType;//bool? if (memberType == typeof(char) || memberType == typeof(char?)) { il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( memberType == typeof(char) ? nameof(SqlMapper.ReadChar) : nameof(SqlMapper.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] //跳转到marklable 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?.IsEnum() == true ? nullUnderlyingType : memberType; if (unboxType.IsEnum()) { Type numericType = Enum.GetUnderlyingType(unboxType); if (colType == typeof(string)) { if (enumDeclareLocal == -1) { enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex; } il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string] StoreLocal(il, enumDeclareLocal); // 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(nameof(Type.GetTypeFromHandle)), null);// stack is now [target][target][enum-type] LoadLocal(il, enumDeclareLocal); // 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] } else { FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); } if (nullUnderlyingType != null) { il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value] } } else if (memberType.FullName == LinqBinary) { il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array] il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary] } else { TypeCode dataTypeCode = TypeExtensions.GetTypeCode(colType), unboxTypeCode = TypeExtensions.GetTypeCode(unboxType); bool hasTypeHandler; if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType)) { if (hasTypeHandler) { #pragma warning disable 618 il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache<int>.Parse)), null); // stack is now [target][target][typed-value] #pragma warning restore 618 } else { il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] } } else { // not a direct match; need to tweak the unbox FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null); if (nullUnderlyingType != null) { il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value] } } } } if (specializedConstructor == null) { // Store the value in the property/field if (item.Property != null) { il.Emit(type.IsValueType() ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); } else { il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] } } //跳转到finishLabel il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value] if (specializedConstructor != null) { il.Emit(OpCodes.Pop); if (item.MemberType.IsValueType()) { int localIndex = il.DeclareLocal(item.MemberType).LocalIndex; LoadLocalAddress(il, localIndex); il.Emit(OpCodes.Initobj, item.MemberType); LoadLocal(il, localIndex); } else { il.Emit(OpCodes.Ldnull); } } else if (applyNullSetting && (!memberType.IsValueType() || Nullable.GetUnderlyingType(memberType) != null)) { il.Emit(OpCodes.Pop); // stack is now [target][target] // can load a null with this value if (memberType.IsValueType()) { // must be Nullable<T> for some T GetTempLocal(il, ref structLocals, memberType, true); // stack is now [target][target][null] } else { // regular reference-type il.Emit(OpCodes.Ldnull); // stack is now [target][target][null] } // Store the value in the property/field if (item.Property != null) { il.Emit(type.IsValueType() ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target] } else { il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] } } else { 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++; } if (type.IsValueType()) { il.Emit(OpCodes.Pop); } else { if (specializedConstructor != null) { il.Emit(OpCodes.Newobj, specializedConstructor); } il.Emit(OpCodes.Stloc_1); // stack is empty #if !NETSTANDARD1_3 if (supportInitialize) { il.Emit(OpCodes.Ldloc_1); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.EndInit)), null); } #endif } il.MarkLabel(allDone); il.BeginCatchBlock(typeof(InvalidCastException)); // stack is Exception il.Emit(OpCodes.Ldloc_0); // stack is Exception, index il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader LoadLocal(il, valueCopyLocal); // stack is Exception, index, reader, value il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.ThrowDataException)), null); il.EndExceptionBlock(); il.Emit(OpCodes.Ldloc_1); // stack is [rval] if (type.IsValueType()) { il.Emit(OpCodes.Box, type); } il.Emit(OpCodes.Ret); var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(IDataReader), returnType); return (Func<IDataReader, object>)dm.CreateDelegate(funcType); }logs_code_collapse">View Code
一个长达三百行的方法,创建一个动态方法:将datarecord转换为目标类型。瞬间放弃了
其次,扩展方法
通读上面的方法GetTypeDeserializerImpl,配合调试
查看生成的动态方法,是这样的
public class Test { public WXDish Deserialize0cfe6fe6-2ac9-4880-8cb7-c838a493b2a6(IDataReader dataReader) { int num = 0; WXDish wXDish = new WXDish(); checked { try { WXDish expr_09 = wXDish; object obj; object expr_DF = obj = ((IDataRecord)this)[num = 7]; if (!(expr_DF is DBNull)) { expr_09.set_IsRecommended((bool)expr_DF); } object expr_101 = obj = ((IDataRecord)this)[num = 8]; if (!(expr_101 is DBNull)) { expr_09.set_IsNew((bool)expr_101); } object expr_124 = obj = ((IDataRecord)this)[num = 9]; if (!(expr_124 is DBNull)) { expr_09.set_IsEnabled((bool?)expr_124); } object expr_147 = obj = ((IDataRecord)this)[num = 10]; if (!(expr_147 is DBNull)) { expr_09.set_IsDiscount((bool?)expr_147); } object expr_16A = obj = ((IDataRecord)this)[num = 11]; if (!(expr_16A is DBNull)) { expr_09.set_DiscountRate((double?)expr_16A); } object expr_18D = obj = ((IDataRecord)this)[num = 12]; if (!(expr_18D is DBNull)) { expr_09.set_IsScore((bool?)expr_18D); } object expr_62A = obj = ((IDataRecord)this)[num = 53]; if (!(expr_62A is DBNull)) { expr_09.set_ImageName((string)expr_62A); } object expr_64D = obj = ((IDataRecord)this)[num = 54]; if (!(expr_64D is DBNull)) { expr_09.set_CookBookCategory((int)expr_64D); } wXDish = expr_09; } catch (Exception arg_66E_0) { object obj; SqlMapper.ThrowDataException(arg_66E_0, num, this, obj); } return wXDish; } } }View Code
测试发现,(bool?)object 0转换错误。
源码中发现
adb5a-3d34-466f-b6a6-2e576ce3ee67" class="code_img_closed" src="/Upload/Images/2017120616/0015B68B3C38AA5B.gif" alt="">if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType)) { if (hasTypeHandler) { #pragma warning disable 618 il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache<int>.Parse)), null); // stack is now [target][target][typed-value] #pragma warning restore 618 } else { il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] } }View Code
考虑通过扩展typehandlers,自定义该类字段转换
public class BoolConvert : SqlMapper.TypeHandler<bool?> { public override void SetValue(IDbDataParameter parameter, bool? value) { // never hit throw new NotImplementedException(); } public override bool? Parse(object value) { var val = value as sbyte?; if (val != null) { var result = Convert.ToBoolean(val); return result; } return (bool?)value; } }View Code
在使用dapper的query前 通过SqlMapper.AddTypeHandler注册该转换
测试通过。1天时间,纪念一下