NHibernate 中的 DateTime 精度以及 NHibernate SchemeExport 中对 DateTime2 的支持

发布于 2024-08-17 04:07:28 字数 2147 浏览 2 评论 0原文

然后,我使用 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 持久保存为 SQL 类型的 datetime2,它可以存储 .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 映射到时间戳,但甚至无法使映射工作,因为插入失败,提示:

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

有谁知道如何让SchemeExport与datetime2一起工作,或者如何让时间戳映射为datetime属性工作?

I am then using Fluent NHibernate and its automapping feature to map the the following simplified POCO class:

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

The CreatedDateTime field will map to a SQL DateTime by default. However if I do a test to check that the entity is being created correctly it fails. This is because the precision of the DateTime field is not maintained through to the SQL database. I undersatnd the reason behind this to be that a MS SQL Server DateTime can only hold milisecond precision by rounded to increments of .000, .003, or .007 (see http://msdn.microsoft.com/en-us/library/ms187819.aspx). For this reason NHibernate truncates the miliseconds when saving to the store. This results in my test failing when checking that the fields where persisted correctly as my .NET DateTime holds its miliseconds but the DateTime retrived after the save has lost its miliseconds and therefore the two are not truely equal.

To overcome this problem I have added the following mapping to the Foo object:

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

I understand that this mapping makes NHibernate persist the CreatedDateTime to a SQL type of datetime2, which can store the full precision that a .NET DateTime can.
This works a treat and the test now passes.

However with one pass comes another fail: My test that checks the schema export now fails with the following error:

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

with a stack trace of:

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)

The code uses the NHibernate.Tool.hbm2ddl.SchemaExport object to call the Execute method.

I am using Fluent v1 and NHibernate v2.1.

I have also tried mapping my DateTime to a TimeStamp but couldn't even get the mapping working as the insert fails stating:

Cannot insert an explicit value into a timestamp column. Use INSERT with a column list to exclude the timestamp column, or insert a DEFAULT into the timestamp column.

Does anyone know either how to get the SchemeExport working with a datetime2 OR how to get timestamp mapping working for a datetime property?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

实际上,NHibernate 参考指出 DateTime nhibernate 类型会将 .NET DateTime 存储为在秒级截断的 SQL 日期时间(无毫秒粒度),

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

此外,请注意,如果您使用过滤器,则相同的规则适用于过滤器定义:如果您指定 DateTime 参数,则该参数的值将被截断,不带毫秒。

查看第 5.2.2 章。基本值类型表 5.3 System.ValueType 映射类型

Actually the NHibernate reference states that the DateTime nhibernate type will store the .NET DateTime as an SQL datetime truncated at the second level (no millisecond granularity)

As such it provides the Timestamp NHibernate type (type="Timestamp" in the mapping) which will store a .NET DateTime as an SQL datetime without truncation. Note here that an SQL timestamp datatype is not needed and will infact break if you have more than one timestamp column in one table. It's thus important to differentiate between the sql-type and type attributes in the NHibernate mapping.

Additionally, note that if you are working with filters, the same rule applies at the filter definition: If you specify a DateTime parameter, the parameter's value will be truncated without milliseconds.

Check out chapter 5.2.2. Basic value types, Table 5.3 System.ValueType Mapping Types.

夜光 2024-08-24 04:07:28

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

这是我设置此约定的约定(使用 Fluent)

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();

使用此约定,您将在存储日期时间时保留纳秒。

For anyone looking to actually keep the nanosecond part of the date, you'll have to use DateTime2 as the sql-column type as well as the Nhibernate DateTime2 type.

Here's my convention for setting this up (using fluent)

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
    }
}

And to activate the convention:

 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();

Using this you'll get to keep nanoseconds when storing your datetimes.

吃兔兔 2024-08-24 04:07:28

我在业务课程上的 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;
    }

I ran into the same problem with a CreatedDate audit field on my business classes. I worked around it by setting the time using the value from a utility method. Hope this helps.

     /// <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;
    }
心是晴朗的。 2024-08-24 04:07:28

在我的域中,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));
    }
}

In my domain it is acceptable to lose the milliseconds from datetimes in SQL Server. I therefore allow a tolerance in my persistance testers using this static helper (nunit implementation):

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));
    }
}
浴红衣 2024-08-24 04:07:28

我能够使用以下方法来解决我的乐观锁定问题:
(使用日期时间2)。

请注意,我使用了此处的名称(以及数据类型名称的大小写):
http://msdn.microsoft.com/en-us/library /system.data.dbtype.aspx
“DateTime2”位于我的映射代码中(在 CustomType 下),而不是 Sql Server 数据类型案例(“datetime2”)。我不确定这是否有什么不同,但我想指出这一点。

流畅的映射:

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)

在 NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j 、对象 oldVersion、对象 obj、SqlCommandInfo sql、ISessionImplementor 会话)
在NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(对象id,对象[]字段,对象[] oldFields,对象rowId,布尔[] includeProperty,Int32 j,对象oldVersion,对象obj,SqlCommandInfo sql,ISessionImplementor会话)
在NHibernate.Persister.Entity.AbstractEntityPersister.Update(对象id,对象[]字段,Int32[] dirtyFields,布尔值hasDirtyCollection,对象[] oldFields,对象oldVersion,对象obj,对象rowId,ISessionImplementor会话)
在 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()

I was able to get my optimistic locking worked out using the below:
(using datetime2).

Note, I used the name (and case of the datatype-name) from here:
http://msdn.microsoft.com/en-us/library/system.data.dbtype.aspx
"DateTime2" is in my mapping code (under CustomType) and not the Sql Server data-type-case ("datetime2"). I'm not sure if that makes a difference but I wanted to point it out.

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; }
}

The Sql Server column is created at:

[MyTimestamp] [datetime2](7) NOT NULL

And my basic tests work and I (correctly) receive an exception like this (when someone else has updted the row)

Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [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)
at NHibernate.Action.EntityUpdateAction.Execute()
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
at NHibernate.Engine.ActionQueue.ExecuteActions()
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Transaction.AdoTransaction.Commit()

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文