花瓣小跑 发表于 2024-12-14 18:28:44

达梦DM.Microsoft.EntityFreameworkCore查询报错invalid cast from DateTim

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()
    {
       = GetDataReaderMethod(nameof(DbDataReader.GetBoolean)),
       = GetDataReaderMethod(nameof(DbDataReader.GetByte)),
       = GetDataReaderMethod(nameof(DbDataReader.GetChar)),
       = GetDataReaderMethod(nameof(DbDataReader.GetDateTime)),
       = GetDataReaderMethod(nameof(DbDataReader.GetDecimal)),
       = GetDataReaderMethod(nameof(DbDataReader.GetDouble)),
       = GetDataReaderMethod(nameof(DbDataReader.GetFloat)),
       = GetDataReaderMethod(nameof(DbDataReader.GetGuid)),
       = GetDataReaderMethod(nameof(DbDataReader.GetInt16)),
       = GetDataReaderMethod(nameof(DbDataReader.GetInt32)),
       = GetDataReaderMethod(nameof(DbDataReader.GetInt64)),
       = GetDataReaderMethod(nameof(DbDataReader.GetString))
    };
    private static MethodInfo GetDataReaderMethod(string name)
      => typeof(DbDataReader).GetRuntimeMethod(name, )!;
    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( 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( 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( TypeMappingSourceDependencies dependencies, 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>();
}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 达梦DM.Microsoft.EntityFreameworkCore查询报错invalid cast from DateTim