实体对象中包含DateTime?、DateTime、int、int?等属性,在使用dapper方法IDbConnect.Query<>()方法时提示了 System.InvalidOperationException异常,经过排除法后确认为DateTime?导致的异常。
这里先来个快速提示,不想看过程的可以直接看标语,有修改方法。
出现这样转换失败的问题找到的是关系映射上类型转换时出错,然后找到了如下方法:
private static Funcobject> 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.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 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;
}
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]
}
}
}
看到它的优雅也就放心了,进来先判断类型是否可强转,不行再根据不同的类型做转换;看到这是不是放心很多,如果是可为空的类型转进来的转换类型是源类型;这个方法就会那么的粗暴。
习惯的结语又到了,有几个点说明下:
1. 贴图的代码都是修改过了的,问题的解决只需要修改unboxTypeCode = TypeExtensions.GetTypeCode(unboxType)
为unboxTypeCode = TypeExtensions.GetTypeCode(nullUnderlyingType??unboxType) 解决类型判断问题就可以了
2. git地址:这个就自己找下吧不贴了
3. 另外再欢迎下大家的吐槽,共同进步