Oracle转SQL Server后NHibernate日期类型兼容问题求助
首先咱们拆解下问题根源:你当前用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

