移植应用程序以从不同的ORM使用NHibernate.
我已经开始实现对内存SQLite数据库运行单元测试的能力.这适用于前几批测试,但我只是遇到了障碍.我们的应用程序将在现实世界中与SQL 2008服务器通信,因此,几个模型当前具有DateTimeOffset属性.在非测试应用程序中映射到/来自SQL 2008时,这一切都正常.
在配置数据库或其他工具时是否有某种机制,以便当我使用来自我的SQLite测试夹具的会话时,DateTimeOffset的东西被"自动神奇地"处理为更平台无关的DateTime?
巧合的是,我今天自己就遇到了这个问题:)我没有彻底测试过这个解决方案,而且我是NHibernate的新手,但它似乎适用于我尝试过的琐碎案例.
首先,您需要创建一个将从DateTimeOffset转换为DateTime的IUserType实现.有一个关于如何在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{ }
我在我的映射中应用了这个约定,就像在这个例子中一样(这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()); } }
另一个允许跟踪原始时区偏移的提议:
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(IAcceptanceCriteriacriteria) { criteria.Expect(x => x.Type == typeof(DateTimeOffset)); } public void Apply(IPropertyInstance instance) { instance.CustomType (); } }