达梦DM.Microsoft.EntityFreameworkCore查询报错invalid cast from DateTime to DateTimeOffset
1. 问题
达梦dotnet efcore的驱动DM.Microsoft.EntityFreameworkCore。
如果实体中存在DateTimeOffset类型字段时,查询报错:invalid cast from DateTime to DateTimeOffset。
Invalid cast from 'System.DateTime' to 'System.DateTimeOffset'
System.Convert.DefaultToType(System.IConvertible, System.Type, System.IFormatProvider)
Dm.DmDataReader.GetFieldValue<T>(int)
同样对于TimeSpan类型的字段,会报错:invalid cast from Double to TimeSpan。
2. 原因
通过反编译DM.Provider驱动的代码,看到Dm.DmDataReader.GetFieldValue方法中非常简单,是直接显示转换成T类型的,没有很好的转换DateTimeOffset和TimeSpan类型的数据,导致报错。
3. 分析
DM.Provider驱动的代码是DM驱动代码,没法重写DmDataReader,那么就只能在DM.EFCore上想办法了,EFCore的实现都是DI依赖注入的方式实现的,所以很多类接口都可以重写,然后重新注入。
EFCore中数据类型映射主要靠RelationalTypeMapping抽象类及其派生类处理的,RelationalTypeMapping抽象类的有关这块获取DataReader数据的代码简化如下:
public abstract class RelationalTypeMapping : CoreTypeMapping
{
private static readonly MethodInfo GetFieldValueMethod
= GetDataReaderMethod(nameof(DbDataReader.GetFieldValue));
private static readonly ConcurrentDictionary<Type, MethodInfo> GetXMethods = new()
{
[typeof(bool)] = GetDataReaderMethod(nameof(DbDataReader.GetBoolean)),
[typeof(byte)] = GetDataReaderMethod(nameof(DbDataReader.GetByte)),
[typeof(char)] = GetDataReaderMethod(nameof(DbDataReader.GetChar)),
[typeof(DateTime)] = GetDataReaderMethod(nameof(DbDataReader.GetDateTime)),
[typeof(decimal)] = GetDataReaderMethod(nameof(DbDataReader.GetDecimal)),
[typeof(double)] = GetDataReaderMethod(nameof(DbDataReader.GetDouble)),
[typeof(float)] = GetDataReaderMethod(nameof(DbDataReader.GetFloat)),
[typeof(Guid)] = GetDataReaderMethod(nameof(DbDataReader.GetGuid)),
[typeof(short)] = GetDataReaderMethod(nameof(DbDataReader.GetInt16)),
[typeof(int)] = GetDataReaderMethod(nameof(DbDataReader.GetInt32)),
[typeof(long)] = GetDataReaderMethod(nameof(DbDataReader.GetInt64)),
[typeof(string)] = GetDataReaderMethod(nameof(DbDataReader.GetString))
};
private static MethodInfo GetDataReaderMethod(string name)
=> typeof(DbDataReader).GetRuntimeMethod(name, [typeof(int)])!;
protected abstract RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters);
public virtual MethodInfo GetDataReaderMethod()
{
var type = (Converter?.ProviderClrType ?? ClrType).UnwrapNullableType();
return GetDataReaderMethod(type);
}
public static MethodInfo GetDataReaderMethod(Type type)
=> GetXMethods.GetOrAdd(type, static t => GetFieldValueMethod.MakeGenericMethod(t));
}
其中虚方法GetDataReaderMethod()返回MethodInfo,它是DataReader的方法(不同的数据库Provider都有一个自己的DataReader实现,本例中达梦的实现就是前面报错堆栈中的Dm.DmDataReader),告诉ef获取数据后如何从DataReader中获取字段值。
这个虚方法GetDataReaderMethod()返回的MethodInfo是从一个私有静态的GetXMethods字典类型中获得的,但这个GetXMethods没有定义DateTimeOffset和TimeSpan,这个时候EF就会调用DataReader.GetFieldValue<T>(int)泛型方法,这个各自数据库实现的泛型方法如果无法处理好各种泛型的话就会报错了。
4. 解决
根据上面的分析,解决似乎也不麻烦了,我们只要重写这个虚方法GetDataReaderMethod(),让它返回正确的MethodInfo以便正确获得值就可以了。
4.1 MyDmDateTimeOffsetTypeMapping
正好DmDataReader中有GetDateTimeOffset(int index)方法,可以改成返回这个方法就能解决问题了,下面就是重写的DmDateTimeOffsetTypeMapping类。
public class MyDmDateTimeOffsetTypeMapping:DmDateTimeOffsetTypeMapping
{
public MyDmDateTimeOffsetTypeMapping([JetBrains.Annotations.NotNull] string storeType, DbType? dbType = System.Data.DbType.DateTimeOffset)
: base(storeType, dbType)
{
}
protected MyDmDateTimeOffsetTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
{
return new MyDmDateTimeOffsetTypeMapping(parameters);
}
public override MethodInfo GetDataReaderMethod()
{
return typeof(DmDataReader).GetRuntimeMethod(nameof(DmDataReader.GetDateTimeOffset), new[] { typeof(int) });
}
}
4.2 MyDmTimeSpanTypeMapping
对于TimeSpan类型要复杂点,因为DmDataReader中没有GetTimeSpan(int index)方法,那么就只能先返回Double的方法GetDouble(int index),之后重写CustomizeDataReaderExpression方法改变下表达式,就是给表达式加点转换代码,代码如下。
public class MyDmTimeSpanTypeMapping : DmTimeSpanTypeMapping
{
public MyDmTimeSpanTypeMapping([JetBrains.Annotations.NotNull] string storeType, DbType? dbType = null)
: base(storeType, dbType)
{
}
protected MyDmTimeSpanTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
{
return new MyDmTimeSpanTypeMapping(parameters);
}
public override MethodInfo GetDataReaderMethod()
{
return typeof(DmDataReader).GetRuntimeMethod(nameof(DmDataReader.GetDouble), new[] { typeof(int) });
}
public override Expression CustomizeDataReaderExpression(Expression expression)
{
return Expression.Call(GetTimeSpanMethod, expression);
}
private static readonly MethodInfo GetTimeSpanMethod
= typeof(MyDmTimeSpanTypeMapping).GetMethod(nameof(GetTimeSpan), new[] { typeof(double) })!;
public static TimeSpan GetTimeSpan(double value)
{
return TimeSpan.FromDays(value);
}
}
重写这两个TypeMapping类一定要注意,需要实现克隆方法protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters),并且返回我们新定义的类,否则后面的GetDataReaderMethod无法运行进来的。
4.3 MyDmTypeMappingSource
现在问题来了,那就是如果把MyDmDateTimeOffsetTypeMapping类加到EFCore中,可以重写RelationalTypeMappingSource的达梦实现类DmTypeMappingSource。
public class MyDmTypeMappingSource : DmTypeMappingSource
{
private MyDmDateTimeOffsetTypeMapping _datetimeoffset = new MyDmDateTimeOffsetTypeMapping("DATETIME WITH TIME ZONE", DbType.DateTimeOffset);
private MyDmDateTimeOffsetTypeMapping _datetimeoffset3 = new MyDmDateTimeOffsetTypeMapping("DATETIME(3) WITH TIME ZONE", DbType.DateTimeOffset);
private MyDmTimeSpanTypeMapping _intervaldt = new MyDmTimeSpanTypeMapping("INTERVAL DAY TO SECOND");
private Dictionary<string, RelationalTypeMapping> _storeTypeMappings;
private Dictionary<Type, RelationalTypeMapping> _clrTypeMappings;
public MyDmTypeMappingSource([JetBrains.Annotations.NotNull] TypeMappingSourceDependencies dependencies, [JetBrains.Annotations.NotNull] RelationalTypeMappingSourceDependencies relationalDependencies)
: base(dependencies, relationalDependencies)
{
_storeTypeMappings = new Dictionary<string, RelationalTypeMapping>(StringComparer.OrdinalIgnoreCase)
{
{ "datetime with time zone", _datetimeoffset },
{ "timestamp with time zone", _datetimeoffset },
{ "datetime(3) with time zone", _datetimeoffset3 },
{ "timestamp(3) with time zone", _datetimeoffset3 },
{ "interval day to second", _intervaldt },
};
_clrTypeMappings = new Dictionary<Type, RelationalTypeMapping>
{
{ typeof(DateTimeOffset), _datetimeoffset },
{ typeof(TimeSpan), _intervaldt }
};
}
protected override RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
{
Type clrType = mappingInfo.ClrType;
string storeTypeName = mappingInfo.StoreTypeName;
string storeTypeNameBase = mappingInfo.StoreTypeNameBase;
if (storeTypeName != null && _storeTypeMappings.ContainsKey(storeTypeName))
return _storeTypeMappings.GetValueOrDefault(storeTypeName)?.Clone(mappingInfo);
if (clrType != null && _clrTypeMappings.ContainsKey(clrType))
return _clrTypeMappings.GetValueOrDefault(clrType)?.Clone(mappingInfo);
return base.FindMapping(mappingInfo);
}
}
4.4 AddEntityFrameworkDm
现在的问题是怎么把新实现的MyDmTypeMappingSource加入到efcore中的问题,只要替换注入IRelationalTypeMappingSource接口实现就可以了。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.ReplaceService<IRelationalTypeMappingSource, MyDmTypeMappingSource>();
}
作者:Rick Carter
出处:http://pains.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [翻译] 为什么 Tracebit 用 C# 开发
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· 刚刚!百度搜索“换脑”引爆AI圈,正式接入DeepSeek R1满血版