You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Oracle转SQL Server后NHibernate日期类型兼容问题求助

解决NHibernate跨Oracle/SQL Server时区类型兼容问题

首先咱们拆解下问题根源:你当前用DateTime类型映射Oracle的TIMESTAMP(6) WITH TIME ZONE,迁移到SQL Server的datetimeoffset(6)后,查询时NHibernate尝试把数据库返回的DateTimeOffset对象强制转成DateTime,但DateTimeOffset并没有实现IConvertible的完整转换逻辑,同时字符串解析格式不匹配,才抛出那两个异常。

下面给你两个可行的方案,都能满足**无需修改映射中sql-type**的需求:

方案1:自定义NHibernate类型(推荐,完全兼容原有实体属性)

创建一个自定义的NHibernate类型,让它自动识别数据库方言,分别处理Oracle的TIMESTAMP WITH TIME ZONE和SQL Server的datetimeoffset(6),同时保持实体的DateTime属性不变。

步骤1:实现自定义类型

using NHibernate;
using NHibernate.Engine;
using NHibernate.SqlTypes;
using Oracle.DataAccess.Types; // 若使用Oracle官方驱动,需引用对应包
using System;
using System.Data;

public class DateTimeWithTimeZoneType : IType
{
    public SqlType[] SqlTypes(ISessionFactoryImplementor factory)
    {
        // 根据方言返回对应SQL类型定义
        if (factory.Dialect is NHibernate.Dialect.Oracle8iDialect)
        {
            return new[] { new SqlType(DbType.DateTimeOffset, 6) };
        }
        else if (factory.Dialect is NHibernate.Dialect.MsSql2008Dialect)
        {
            return new[] { new SqlType(DbType.DateTimeOffset, 6) };
        }
        throw new NotSupportedException("当前数据库方言未被支持");
    }

    public object NullSafeGet(IDataReader rs, string[] names, ISessionImplementor session, object owner)
    {
        var value = rs[names[0]];
        if (value == DBNull.Value) return null;

        // 处理SQL Server返回的DateTimeOffset
        if (value is DateTimeOffset dateTimeOffset)
        {
            // 可根据业务需求转成UTC或本地时间,示例用UTC
            return dateTimeOffset.UtcDateTime;
        }
        // 处理Oracle返回的TimestampWithTimeZone
        if (value is OracleTimeStampTZ oracleTs)
        {
            return oracleTs.Value;
        }
        // 兜底转换
        return Convert.ToDateTime(value);
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index, ISessionImplementor session)
    {
        if (value == null)
        {
            cmd.Parameters[index].Value = DBNull.Value;
            return;
        }

        var dateTime = (DateTime)value;
        // 根据方言设置参数值
        if (session.Factory.Dialect is NHibernate.Dialect.Oracle8iDialect)
        {
            cmd.Parameters[index].Value = new OracleTimeStampTZ(dateTime);
        }
        else if (session.Factory.Dialect is NHibernate.Dialect.MsSql2008Dialect)
        {
            // 假设业务存储UTC时间,可按需调整时区偏移
            cmd.Parameters[index].Value = new DateTimeOffset(dateTime, TimeSpan.Zero);
        }
        else
        {
            cmd.Parameters[index].Value = dateTime;
        }
    }

    // 实现IType接口的其他必要方法
    public string Name => "DateTimeWithTimeZone";
    public Type ReturnedType => typeof(DateTime?);
    public bool IsMutable => false;
    public bool IsDirty(object old, object current, ISessionImplementor session)
    {
        return !object.Equals(old, current);
    }

    // 其他接口方法如DeepCopy、Disassemble等可按需实现,此处省略基础逻辑
}

步骤2:修改映射文件

把原来的type="DateTime"替换成你自定义的类型(注意替换对应命名空间和程序集名称):

<property name="checkoutDate" type="YourProject.NHibernate.Types.DateTimeWithTimeZoneType, YourProject">
    <column name="CHECKOUT_DATE" sql-type="TIMESTAMP(6) WITH TIME ZONE" not-null="false" />
</property>

切换数据库驱动时,自定义类型会自动适配对应数据库类型,完全不用修改sql-type。

方案2:改用NHibernate内置的DateTimeOffset类型(需修改实体属性)

如果业务允许把实体属性从DateTime?改成DateTimeOffset?,可以直接使用NHibernate的内置DateTimeOffset类型,它能自动兼容两种数据库的时区类型:

步骤1:修改实体属性

将实体中的checkoutDate类型改为DateTimeOffset?。

步骤2:修改映射文件

将type改为DateTimeOffset,保留原有的sql-type:

<property name="checkoutDate" type="DateTimeOffset">
    <column name="CHECKOUT_DATE" sql-type="TIMESTAMP(6) WITH TIME ZONE" not-null="false" />
</property>

NHibernate的方言会自动处理:Oracle环境下把TIMESTAMP(6) WITH TIME ZONE映射为DateTimeOffset,SQL Server环境下把datetimeoffset(6)映射为DateTimeOffset,无需额外配置。

补充说明

不存在同时兼容Oracle和SQL Server的通用sql-type字符串,因为两种数据库的类型命名本身就不一致。上面的两个方案都是通过NHibernate的类型系统做适配,从而达到不用修改sql-type的目的。

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:18:43