NHibernate中的DateTime精度和NHibernate SchemeExport中对DateTime2的支持

时间:2010-01-09 14:23:14

标签: nhibernate datetime fluent-nhibernate timestamp granularity

然后我使用Fluent NHibernate及其自动化功能来映射以下简化的POCO类:

public class Foo
{    
public virtual int Id { get; set; }    
public virtual datetime CreatedDateTime { get; set; }    
}

默认情况下,CreatedDateTime字段将映射到SQL DateTime。但是,如果我进行测试以检查实体是否正确创建,则会失败。这是因为DateTime字段的精度不会保留到SQL数据库。我强调这背后的原因是MS SQL Server DateTime只能通过四舍五入到.000,.003或.007的增量来保持毫秒精度(见http://msdn.microsoft.com/en-us/library/ms187819.aspx)。因此,NHibernate在保存到商店时会截断毫秒数。这导致我的测试失败时检查正确持久的字段,因为我的.NET DateTime保持其毫秒,但是在保存失去其毫秒之后重新启动了DateTime,因此两者不是真正相等。

为了克服这个问题,我将以下映射添加到Foo对象:

public class FooMap : IAutoMappingOverride<Foo>
{
    public void Override(AutoMapping<Foo> mapping)
    {
        mapping.Map(f => f.CreatedDateTime).CustomType("datetime2");     
    }
}

据我所知,这种映射使得NHibernate将CreatedDateTime持久化为datetime2的SQL类型,该类型可以存储.NET DateTime的完整精度。 这是一种享受,现在测试通过了。

然而,一次传递另一次失败:我的检查架构导出的测试现在失败,出现以下错误:

System.ArgumentException : Dialect does not support DbType.DateTime2
Parameter name: typecode

堆栈跟踪:

at NHibernate.Dialect.TypeNames.Get(DbType typecode)
at NHibernate.Dialect.Dialect.GetTypeName(SqlType sqlType)
at NHibernate.Mapping.Column.GetDialectTypeName(Dialect dialect, IMapping mapping)
at NHibernate.Mapping.Table.SqlCreateString(Dialect dialect, IMapping p, String defaultCatalog, String defaultSchema)
at NHibernate.Cfg.Configuration.GenerateSchemaCreationScript(Dialect dialect)
at NHibernate.Tool.hbm2ddl.SchemaExport..ctor(Configuration cfg, IDictionary`2 configProperties)
at NHibernate.Tool.hbm2ddl.SchemaExport..ctor(Configuration cfg)

代码使用NHibernate.Tool.hbm2ddl.SchemaExport对象来调用Execute方法。

我正在使用Fluent v1和NHibernate v2.1。

我还尝试将我的DateTime映射到TimeStamp,但由于插入失败说明,甚至无法使映射正常工作:

无法将显式值插入时间戳列。将INSERT与列列表一起使用以排除时间戳列,或将DEFAULT插入时间戳列。

有没有人知道如何让SchemeExport使用datetime2或如何让时间戳映射适用于datetime属性?

5 个答案:

答案 0 :(得分:32)

实际上NHibernate引用声明DateTime nhibernate类型将.NET DateTime存储为在第二级截断的SQL日期时间(无毫秒粒度)

因此它在映射中提供Timestamp NHibernate类型(type="Timestamp"),它将.NET DateTime存储为SQL datetime而不截断。请注意,SQL timestamp数据类型需要,如果一个表中有多个timestamp列,则会因此而中断。因此,区分NHibernate映射中的sql-typetype属性非常重要。

此外,请注意,如果您正在使用过滤器,则在过滤器定义中应用相同的规则:如果指定DateTime参数,则参数的值将被截断而不会毫秒。

结帐chapter 5.2.2. Basic value typesTable 5.3 System.ValueType Mapping Types

答案 1 :(得分:5)

对于任何想要实际保留日期的纳秒部分的人,您必须使用DateTime2作为sql-column类型以及Nhibernate DateTime2类型。

这是我设置它的惯例(使用流利的)

public class DateTimeConvention : IPropertyConvention, IPropertyConventionAcceptance
{

    public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
    {
        criteria.Expect(x => x.Type == typeof(DateTime) || x.Type == typeof(DateTime?));
    }
    public void Apply(IPropertyInstance instance)
    {
        instance.CustomSqlType("DateTime2"); //specify that the sql column is DateTime2
        instance.CustomType("DateTime2"); //set the nhib type as well
    }
}

并激活惯例:

 var v = Fluently.Configure()
         .Database(MsSqlConfiguration.MsSql2008
         .ConnectionString(d => d.FromConnectionStringWithKey("connstring"))
         .ShowSql())
         .Mappings(m => m.FluentMappings.AddFromAssemblyOf<IRepository>()
         .Conventions.AddFromAssemblyOf<IRepository>()) //this adds your convention
         .BuildSessionFactory();

使用此功能,您可以在存储日期时保持纳秒。

答案 2 :(得分:1)

我在业务类上遇到了与CreatedDate审核字段相同的问题。我通过使用实用程序方法中的值设置时间来解决它。希望这会有所帮助。

     /// <summary>
    /// Return a DateTime with millisecond resolution to be used as the timestamp. This is needed so that DateTime of an existing instance
    /// will equal one that has been persisted and returned from the database. Without this, the times differ due to different resolutions.
    /// </summary>
    /// <returns></returns>
    private DateTime GetTime()
    {
        var now = DateTime.Now;
        var ts = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond, DateTimeKind.Local);
        return ts;
    }

答案 3 :(得分:0)

在我的域中,可以从SQL Server中的日期时间中丢失毫秒数。因此,我允许使用此静态帮助程序(nunit实现)的持久性测试程序中的容差:

public static class AssertDateTime
{
    /// <summary>
    /// Checks that the DateTimes are no more than second apart
    /// </summary>
    /// <param name="Expected"></param>
    /// <param name="Actual"></param>
    public static void AreWithinOneSecondOfEachOther(DateTime Expected, DateTime Actual)
    {
        var timespanBetween = Actual.Subtract(Expected);

        if (timespanBetween > TimeSpan.FromSeconds(1))
            Assert.Fail(string.Format("The times were more than a second appart. They were out by {0}. Expected {1}, Actual {2}.", timespanBetween, Expected, Actual));
    }
}

答案 4 :(得分:0)

我能够使用以下方法解决我的乐观锁定问题: (使用datetime2)。

注意,我从这里使用了名称(以及datatype-name的大小写): http://msdn.microsoft.com/en-us/library/system.data.dbtype.aspx “DateTime2”位于我的映射代码中(在CustomType下),而不是Sql Server数据类型大小写(“datetime2”)。我不确定这是否有所作为,但我想指出它。

Fluent Mapping:

public class DogBreedMap : ClassMap<DogBreed>
{
    public DogBreedMap()
    {
        Id(x => x.DogBreedUUID).GeneratedBy.GuidComb();
        OptimisticLock.Version();
        Version(x => x.Version)
           .Column("MyTimestamp").CustomType("DateTime2");
    }
}




public partial class DogBreed
{

    public DogBreed()
    {
        CommonConstructor();
    }

    private void CommonConstructor()
    {
        this.Version = DateTime.MinValue; /*I don't think this is necessary*/
    }

    public virtual Guid? DogBreedUUID { get; set; }

    public virtual DateTime Version { get; set; }
}

Sql Server列创建于:

[MyTimestamp] [datetime2](7) NOT NULL

我的基本测试工作正常,我(正确)收到这样的异常(当其他人更新了行时)

行被另一个事务更新或删除(或未保存的值映射不正确):[DogBreed#abcabc1d-abc4-abc9-abcb-abca01140a27]

at NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32 rows, Object id, Int32 tableNumber, IExpectation expectation, IDbCommand statement)

at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id,Object [] fields,Object [] oldFields,Object rowId,Boolean [] includeProperty,Int32 j,Object oldVersion,Object obj,SqlCommandInfo sql,ISessionImplementor session)    at NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(Object id,Object [] fields,Object [] oldFields,Object rowId,Boolean [] includeProperty,Int32 j,Object oldVersion,Object obj,SqlCommandInfo sql,ISessionImplementor session)    at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id,Object [] fields,Int32 [] dirtyFields,Boolean hasDirtyCollection,Object [] oldFields,Object oldVersion,Object obj,Object rowId,ISessionImplementor session)    在NHibernate.Action.EntityUpdateAction.Execute()    在NHibernate.Engine.ActionQueue.Execute(IExecutable可执行文件)    在NHibernate.Engine.ActionQueue.ExecuteActions(IList列表)    在NHibernate.Engine.ActionQueue.ExecuteActions()    在NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource会话)    在NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent事件)    在NHibernate.Impl.SessionImpl.Flush()    在NHibernate.Transaction.AdoTransaction.Commit()