达梦DM.Microsoft.EntityFreameworkCore查询报错invalid cast from DateTime to DateTimeOffset

news/2024/12/14 14:53:10/文章来源:https://www.cnblogs.com/pains/p/18605329

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(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 FindMapping(mappingInfo);}
}

4.4 AddEntityFrameworkDm

现在的问题是怎么把新实现的MyDmTypeMappingSource加入到efcore中的问题,只要替换注入IRelationalTypeMappingSource接口实现就可以了。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{base.OnConfiguring(optionsBuilder);optionsBuilder.ReplaceService<IRelationalTypeMappingSource, MyDmTypeMappingSource>();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/852514.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024-2025-1 20241318 《计算机基础与程序设计》第十二周学习总结

这个作业属于哪个课程 https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP这个作业要求在哪里 https://www.cnblogs.com/rocedu/p/9577842.html#WEEK12这个作业的目标 <自学教材《C语言程序设计》第11章并完成云班课测试>| |作业正文|https://i.cnblogs.com/posts/e…

集合的数据筛选

1.集合的数据筛选 1.创建集合,并封装数据2.许愿有一个方法,可以判断年龄,然后alt+回车创建该方法(忽略这里未封装数据)3.在方法中,创建新的集合,存储年龄小于18的对象4.遍历list集合,每一个元素都有可能小于18,再使用if判断,将满足条件的元素存入newlist集合5.在主方法…

转载:【AI系统】动手实现自动微分

在这章内容,会介绍是怎么实现自动微分的,因为代码量非常小,也许你也可以写一个玩玩。前面的文章当中,已经把自动微分的原理深入浅出的讲了一下,也引用了非常多的论文。有兴趣的可以顺着综述 A survey 这篇深扒一下。 前向自动微分原理 了解自动微分的不同实现方式非常有用…

基于.NET8+Vue3开发的权限管理个人博客系统

前言 今天大姚给大家分享一个基于.NET8+Vue3开发的权限管理&个人博客系统:Easy.Admin。 项目介绍 Easy.Admin是一个基于.NET8+Vue3+TypeScript开发的权限管理&个人博客系统,分为普通版本和SSR(服务端渲染,支持SEO),服务端渲染框架基于vite-plugin-ssr实现,并且支…

spring-boot-devtools 实现热部署

1.devtoolsspring为开发者提供了一个名为spring-boot-devtools的模块来使Spring Boot应用支持热部署,提高开发者的开发效率,无需手动重启Spring Boot应用。 2.项目搭建本文是采用IDEA搭建的Spring Boot应用,通过spring-boot-devtools配置,可以支持修改java文件会自动重启程…

IDEA bean json互转换插件

插件安装步骤:File->Settings->Plugins—>查找所需插件—>Install 或 File->Settings->Plugins—>Install plug from disk —>选择下载好的插件安装 一般插件安装后重启idea即可生效。 一、Java bean 转换 json 的插件 java-bean-to-json 下面详细安装…

转载:【AI系统】计算图的调度与执行

在前面的内容介绍过,深度学习的训练过程主要分为以下三个部分:1)前向计算、2)计算损失、3)更新权重参数。在训练神经网络时,前向传播和反向传播相互依赖。对于前向传播,沿着依赖的方向遍历计算图并计算其路径上的所有变量。然后将这些用于反向传播,其中计算顺序与计算图…

转载:【AI系统】微分实现方式

上一篇文章简单了解计算机中常用几种微分方式。本文将深入介绍 AI 框架离不开的核心功能:自动微分。 而自动微分则是分为前向微分和后向微分两种实现模式,不同的实现模式有不同的机制和计算逻辑,而无论哪种模式都离不开雅克比矩阵,所以我们也会深入了解一下雅克比矩阵的原理…

React16

React16免费基础视频教程 https://www.bilibili.com/video/BV1g4411i7po P1 01_React免费视频课程介绍 https://jspang.com 2019 5年前 react16 16.8.6 https://react.dev/ P2 02_React简介和Vue的对比 P3 03_React开发环境的搭建 npm i -g create-react-app@3.0.0 create-reac…

Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same

错误报在了forward里的Conv2d处。原因是函数写在forward里可能默认cpu,如果写在init构造函数里,就不需要再指定cuda。 修改为箭头指示就不再报错了。 【参考】 Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same-CSDN博客

jquery半透明拖拽窗口插件

这是一款jquery半透明拖拽窗口插件。该插件可以在页面生成可以拖拽、最大化、最小化的浮动窗口。在线演示 下载使用方法 在页面中引入style.css、jquery和jquery-translucent.js文件。<link rel="stylesheet" type="text/css" href="style.css&quo…