Dapper.net Nullable<DateTime>类型数据转换时异常问题解决

来源:互联网 发布:网上代理销售软件 编辑:程序博客网 时间:2024/05/21 15:34

0x01运行环境

  1. dapper 版本1.50.2
  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;            }
  1. 变量“colType”是数据库中的数据类型(也就是的Idbreader中取到的类型DbType)
  2. 变量“memberType”是实体属性或者字段类型
    上面两上变量的作用很大,确认两个类型;下面继续来看
    Dapper.net   Nullable<DateTime>类型数据转换时异常问题解决
    上图中的if对char/char?类型做了特殊处理,在eles中首先做了判断colType是不是DbNull类型,然后就是对memberType做类型判断了,确认memberType的基本类型;使用Nullable.GetUnderlyingType()来判断是不是Nullable类型,这就很重要了;我们的重点。
    下面再来看下面的代码,数据转换部分:
    Dapper.net   Nullable<DateTime>类型数据转换时异常问题解决
    图中的代码已经修改过了,之前在获取 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. 另外再欢迎下大家的吐槽,共同进步

原创粉丝点击