Error parsing column 8 (IsRecommended=0 - SByte) Dapper查询mysql数据库可空的tinyint(1)一个错误

时间:2022-06-14 06:21:16

出错条件:

  1.实体属性为bool?类型

  2.对应字段为可空的tinyint(1)类型

  3.该字段查询结果内即含有null,又含有正常值

google答案,两种建议:

1.修改sql语句,直接cast转换(未通过)

2.修改字段类型为tinyint长度为2或更长(此法可行,测试发现,改成bit类型也行),在datareader 的getfieldtype时,tinyint长度为1类型为Boolean,大于1时类型为byte,short,int

然,因我的查询结果中此类字段数量较多,且以上两种方案并不满意,也不符合我的需求,故想自己另觅答案:

首先,想到修改源码,为开源做贡献。发现它是长这样的:

private static Func<IDataReader, object> GetTypeDeserializerImpl(
Type type, IDataReader reader, int startBound = , int length = -, 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 == -)
{
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));
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 == )
{
//用指定构造函数创建一个新实例,赋值给类型变量
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));// [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 = -, 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 == -)
{
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);
}

一个长达三百行的方法,创建一个动态方法:将datarecord转换为目标类型。瞬间放弃了

其次,扩展方法

通读上面的方法GetTypeDeserializerImpl,配合调试

查看生成的动态方法,是这样的

public class Test
{
public WXDish Deserialize0cfe6fe6-2ac9--8cb7-c838a493b2a6(IDataReader dataReader)
{
int num = ;
WXDish wXDish = new WXDish();
checked
{
try
{
WXDish expr_09 = wXDish;
object obj;
object expr_DF = obj = ((IDataRecord)this)[num = ];
if (!(expr_DF is DBNull))
{
expr_09.set_IsRecommended((bool)expr_DF);
}
object expr_101 = obj = ((IDataRecord)this)[num = ];
if (!(expr_101 is DBNull))
{
expr_09.set_IsNew((bool)expr_101);
}
object expr_124 = obj = ((IDataRecord)this)[num = ];
if (!(expr_124 is DBNull))
{
expr_09.set_IsEnabled((bool?)expr_124);
}
object expr_147 = obj = ((IDataRecord)this)[num = ];
if (!(expr_147 is DBNull))
{
expr_09.set_IsDiscount((bool?)expr_147);
}
object expr_16A = obj = ((IDataRecord)this)[num = ];
if (!(expr_16A is DBNull))
{
expr_09.set_DiscountRate((double?)expr_16A);
}
object expr_18D = obj = ((IDataRecord)this)[num = ];
if (!(expr_18D is DBNull))
{
expr_09.set_IsScore((bool?)expr_18D);
}
object expr_62A = obj = ((IDataRecord)this)[num = ];
if (!(expr_62A is DBNull))
{
expr_09.set_ImageName((string)expr_62A);
}
object expr_64D = obj = ((IDataRecord)this)[num = ];
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;
}
}
}

测试发现,(bool?)object 0转换错误。

源码中发现

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]
}
}

考虑通过扩展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;
}
}

在使用dapper的query前 通过SqlMapper.AddTypeHandler注册该转换

测试通过。1天时间,纪念一下