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);