Dapper.net Nullable<DateTime>类型数据转换时异常问题解决
来源:互联网 发布:网上代理销售软件 编辑:程序博客网 时间:2024/05/21 15:34
0x01运行环境
- dapper 版本1.50.2
- .net framework 4.5
0x02问题描述
实体对象中包含DateTime?、DateTime、int、int?等属性,在使用dapper方法IDbConnect.Query<>()方法时提示了 System.InvalidOperationException异常,经过排除法后确认为DateTime?导致的异常。
0x03问题定位
这里先来个快速提示,不想看过程的可以直接看标语,有修改方法。
出现这样转换失败的问题找到的是关系映射上类型转换时出错,然后找到了如下方法:
private static Func<IDataReader, object> GetTypeDeserializerImpl( Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false )
这个方法主要做用是映射实体与sql查询结果的关系,其中包含了数据类型的转换,下面来对主要部分剖析下;在剖析前说明下dapper做类型转换用的两种方法分别为“类型强转”与相关类型“ Parse”方法转换,这是对基本类型的转换方式
//.......var members = (specializedConstructor != null ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n)) : names.Select(n => typeMap.GetMember(n))).ToList();//....... members是定义的实体属性、字段等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(); 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); Type memberType = item.MemberType; 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] 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; //var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum() ? 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(nullUnderlyingType??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] } } 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 += 1; }
- 变量“colType”是数据库中的数据类型(也就是的Idbreader中取到的类型DbType)
- 变量“memberType”是实体属性或者字段类型
上面两上变量的作用很大,确认两个类型;下面继续来看
上图中的if对char/char?类型做了特殊处理,在eles中首先做了判断colType是不是DbNull类型,然后就是对memberType做类型判断了,确认memberType的基本类型;使用Nullable.GetUnderlyingType()来判断是不是Nullable类型,这就很重要了;我们的重点。
下面再来看下面的代码,数据转换部分:
图中的代码已经修改过了,之前在获取 unboxTypeCode这个变量值时的写法是TypeExtensions.GetTypeCode(unboxType)
如果unboxType的值是 Nullable< DateTime>类型时获取到TypeCode是object,在if做判断时就会走类型的强转步骤,最后导致转换失败;所以将关键在于unboxTypeCode;使用它来影响下面的if判断,让代码走到第二个断点;为什么要走到第二个断点呢,先来看方法FlexibleConvertBoxedFromHeadOfStack
的代码:
首先调用方式FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null)
调用中判断了类型nullUnderlyingType ?? unboxType
可为空类型取值时是源类型。然后来看方法:
private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type from, Type to, Type via) { MethodInfo op; if(from == (via ?? to)) { il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] } else if ((op = GetOperator(from,to)) != null) { // this is handy for things like decimal <===> double il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][data-typed-value] il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value] } else { bool handled = false; OpCode opCode = default(OpCode); switch (TypeExtensions.GetTypeCode(from)) { case TypeCode.Boolean: case TypeCode.Byte: case TypeCode.SByte: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: handled = true; switch (TypeExtensions.GetTypeCode(via ?? to)) { case TypeCode.Byte: opCode = OpCodes.Conv_Ovf_I1_Un; break; case TypeCode.SByte: opCode = OpCodes.Conv_Ovf_I1; break; case TypeCode.UInt16: opCode = OpCodes.Conv_Ovf_I2_Un; break; case TypeCode.Int16: opCode = OpCodes.Conv_Ovf_I2; break; case TypeCode.UInt32: opCode = OpCodes.Conv_Ovf_I4_Un; break; case TypeCode.Boolean: // boolean is basically an int, at least at this level case TypeCode.Int32: opCode = OpCodes.Conv_Ovf_I4; break; case TypeCode.UInt64: opCode = OpCodes.Conv_Ovf_I8_Un; break; case TypeCode.Int64: opCode = OpCodes.Conv_Ovf_I8; break; case TypeCode.Single: opCode = OpCodes.Conv_R4; break; case TypeCode.Double: opCode = OpCodes.Conv_R8; break; default: handled = false; break; } break; } if (handled) { il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][col-typed-value] il.Emit(opCode); // stack is now [target][target][typed-value] if (to == typeof(bool)) { // compare to zero; I checked "csc" - this is the trick it uses; nice il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ceq); } } else { il.Emit(OpCodes.Ldtoken, via ?? to); // stack is now [target][target][value][member-type-token] il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)), null); // stack is now [target][target][value][member-type] il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value] il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] } } }
看到它的优雅也就放心了,进来先判断类型是否可强转,不行再根据不同的类型做转换;看到这是不是放心很多,如果是可为空的类型转进来的转换类型是源类型;这个方法就会那么的粗暴。
0x04结语
习惯的结语又到了,有几个点说明下:
1. 贴图的代码都是修改过了的,问题的解决只需要修改unboxTypeCode = TypeExtensions.GetTypeCode(unboxType)
为unboxTypeCode = TypeExtensions.GetTypeCode(nullUnderlyingType??unboxType) 解决类型判断问题就可以了
2. git地址:这个就自己找下吧不贴了
3. 另外再欢迎下大家的吐槽,共同进步
阅读全文
0 0
- Dapper.net Nullable<DateTime>类型数据转换时异常问题解决
- Dapper MySql DateTime 异常
- 把string类型的时间数据,转换成datetime类型。
- 可空类型的转换问题(?|nullable)
- C# Nullable的类型转换问题
- 谈谈Nullable<T>的类型转换问题
- 数据插入类型转换异常
- .net 2.0中新增的nullable类型
- .net 2.0中新增的nullable类型
- .net 2.0中新增的nullable类型
- C#将数据转换为指定类型,支持对可空类型(Nullable类)转换方法示例
- C#将数据转换为指定类型,支持对可空类型(Nullable类)转换方法示例
- C#将数据转换为指定类型,支持对可空类型(Nullable类)转换方法示例
- DateTime类型转换比较
- Json 的日期格式与.Net DateTime类型的转换
- databindings 绑定Nullable<DateTime>
- 将时间类型DateTime数据转换成毫秒Int型
- nullable类型
- 用jquery制作全网热播视频页面
- 有关Spirng ioc的BeanPostProcessor接口案例
- LoRaWAN协议
- LeetCode编程练习
- 微信小程序-导航栏样式、tabBar导航栏
- Dapper.net Nullable<DateTime>类型数据转换时异常问题解决
- spark 之 把hive里的表和myslq 里的表做计算
- 使用Object.definePropery方法定义一个只读对象实例
- java装箱和拆箱的记录
- java 反射技术:加载类,解析类的组成部分
- Pandas基础复习-Series
- 自定义View控件点击随机生成4位数
- 冒泡排序
- Android自定义View(一、初体验自定义TextView)