使用 SQLite 和 DateTimeOffset 映射对 NHibernate 进行单元测试

问题描述 投票:0回答:4

移植应用程序以从不同的 ORM 使用 NHibernate。

我已经开始具备针对内存 SQLite 数据库运行单元测试的能力。这适用于前几批测试,但我遇到了障碍。在现实世界中,我们的应用程序将与 SQL 2008 服务器通信,因此,当前有多个模型具有 DateTimeOffset 属性。在非测试应用程序中映射到 SQL 2008 或从 SQL 2008 映射时,这一切都工作正常。

在配置数据库或其他设施时是否有某种机制,以便当我使用 SQLite 测试装置中的会话时,DateTimeOffset 内容会“自动神奇地”处理为与平台无关的 DateTime?

nhibernate sqlite datetimeoffset
4个回答
14
投票

巧合的是,我今天自己也遇到了这个问题:)我还没有彻底测试这个解决方案,而且我是NHibernate的新手,但它似乎在我尝试过的简单情况下工作。

首先,您需要创建一个 IUserType 实现,它将从 DateTimeOffset 转换为 DateTime。 Ayende 博客上有一个关于如何创建用户类型的完整示例,但与我们的目的相关的方法实现是:

public class NormalizedDateTimeUserType : IUserType { private readonly TimeZoneInfo databaseTimeZone = TimeZoneInfo.Local; // Other standard interface implementations omitted ... public Type ReturnedType { get { return typeof(DateTimeOffset); } } public SqlType[] SqlTypes { get { return new[] { new SqlType(DbType.DateTime) }; } } public object NullSafeGet(IDataReader dr, string[] names, object owner) { object r = dr[names[0]]; if (r == DBNull.Value) { return null; } DateTime storedTime = (DateTime)r; return new DateTimeOffset(storedTime, this.databaseTimeZone.BaseUtcOffset); } public void NullSafeSet(IDbCommand cmd, object value, int index) { if (value == null) { NHibernateUtil.DateTime.NullSafeSet(cmd, null, index); } else { DateTimeOffset dateTimeOffset = (DateTimeOffset)value; DateTime paramVal = dateTimeOffset.ToOffset(this.databaseTimeZone.BaseUtcOffset).DateTime; IDataParameter parameter = (IDataParameter)cmd.Parameters[index]; parameter.Value = paramVal; } } }

databaseTimeZone

字段包含一个
TimeZone
,它描述了用于在数据库中存储值的时区。所有 
DateTimeOffset
 值在存储前都会转换为该时区。在我当前的实现中,它被硬编码为本地时区,但您始终可以定义 ITimeZoneProvider 接口并将其注入构造函数中。

为了在不修改所有类映射的情况下使用此用户类型,我在 Fluent NH 中创建了一个约定:

public class NormalizedDateTimeUserTypeConvention : UserTypeConvention<NormalizedDateTimeUserType> { }

我在我的映射中应用了这个约定,如本例所示(

new NormalizedDateTimeUserTypeConvention()

是重要的部分):

mappingConfiguration.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()) .Conventions.Add( PrimaryKey.Name.Is(x => x.EntityType.Name + "Id"), new NormalizedDateTimeUserTypeConvention(), ForeignKey.EndsWith("Id"));

就像我说的,这还没有经过彻底的测试,所以要小心!但现在,我需要做的就是更改一行代码(流畅的映射规范),我就可以在数据库中的 DateTime 和 DateTimeOffset 之间切换。


编辑

根据要求,Fluent NHibernate 配置:

为 SQL Server 构建会话工厂:

private static ISessionFactory CreateSessionFactory(string connectionString) { return Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)) .Mappings(m => MappingHelper.SetupMappingConfiguration(m, false)) .BuildSessionFactory(); }

对于 SQLite:

return Fluently.Configure() .Database(SQLiteConfiguration.Standard.InMemory) .Mappings(m => MappingHelper.SetupMappingConfiguration(m, true)) .ExposeConfiguration(cfg => configuration = cfg) .BuildSessionFactory();

SetupMappingConfiguration的实现:

public static void SetupMappingConfiguration(MappingConfiguration mappingConfiguration, bool useNormalizedDates) { mappingConfiguration.FluentMappings .AddFromAssembly(Assembly.GetExecutingAssembly()) .Conventions.Add( PrimaryKey.Name.Is(x => x.EntityType.Name + "Id"), ForeignKey.EndsWith("Id")); if (useNormalizedDates) { mappingConfiguration.FluentMappings.Conventions.Add(new NormalizedDateTimeUserTypeConvention()); } }
    

5
投票
另一个提案允许

跟踪原始时区偏移量

public class DateTimeOffsetUserType : ICompositeUserType { public string[] PropertyNames { get { return new[] { "LocalTicks", "Offset" }; } } public IType[] PropertyTypes { get { return new[] { NHibernateUtil.Ticks, NHibernateUtil.TimeSpan }; } } public object GetPropertyValue(object component, int property) { var dto = (DateTimeOffset)component; switch (property) { case 0: return dto.UtcTicks; case 1: return dto.Offset; default: throw new NotImplementedException(); } } public void SetPropertyValue(object component, int property, object value) { throw new NotImplementedException(); } public Type ReturnedClass { get { return typeof(DateTimeOffset); } } public new bool Equals(object x, object y) { if (ReferenceEquals(x, null) && ReferenceEquals(y, null)) return true; if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false; return x.Equals(y); } public int GetHashCode(object x) { return x.GetHashCode(); } public object NullSafeGet(IDataReader dr, string[] names, ISessionImplementor session, object owner) { if (dr.IsDBNull(dr.GetOrdinal(names[0]))) { return null; } var dateTime = (DateTime)NHibernateUtil.Ticks.NullSafeGet(dr, names[0], session, owner); var offset = (TimeSpan)NHibernateUtil.TimeSpan.NullSafeGet(dr, names[1], session, owner); return new DateTimeOffset(dateTime, offset); } public void NullSafeSet(IDbCommand cmd, object value, int index, ISessionImplementor session) { object utcTicks = null; object offset = null; if (value != null) { utcTicks = ((DateTimeOffset)value).DateTime; offset = ((DateTimeOffset)value).Offset; } NHibernateUtil.Ticks.NullSafeSet(cmd, utcTicks, index++, session); NHibernateUtil.TimeSpan.NullSafeSet(cmd, offset, index, session); } public object DeepCopy(object value) { return value; } public bool IsMutable { get { return false; } } public object Disassemble(object value, ISessionImplementor session) { return value; } public object Assemble(object cached, ISessionImplementor session, object owner) { return cached; } public object Replace(object original, object target, ISessionImplementor session, object owner) { return original; } }

DateTimeOffset ICompositeUserType 的流畅 NNibernate 约定将是:

public class DateTimeOffsetTypeConvention : IPropertyConvention, IPropertyConventionAcceptance { public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria) { criteria.Expect(x => x.Type == typeof(DateTimeOffset)); } public void Apply(IPropertyInstance instance) { instance.CustomType<DateTimeOffsetUserType>(); } }
    

3
投票
由于我缺乏代表,我无法将其添加为已接受答案的评论,但想添加一些我在已接受答案中实现解决方案时发现的附加信息。调用架构导出时,我也收到方言不支持 DateTimeOffset 的错误。添加 log4net 日志记录支持后,我发现我的属性的类型为 DateTimeOffset?不属于公约所处理的范围。也就是说,该约定未应用于

nullable DateTimeOffset 属性。

为了解决这个问题,我创建了一个派生自 NormalizedDateTimeUserType 的类并覆盖 ReturnedType 属性(必须将原始属性标记为虚拟)。然后,我为派生类创建了第二个 UserTypeConvention,最后将第二个约定添加到我的配置中。


0
投票
在上面尝试过,但我得到一个错误,说“TypeLOadException”:通用参数[0],“System.Nullable 上的 system.nullable {system.datetimeOffset} 违反了类型参数 T 的约束

© www.soinside.com 2019 - 2024. All rights reserved.