Подтвердить что ты не робот

Точность DateTime в NHibernate и поддержка DateTime2 в NHibernate SchemeExport

Затем я использую 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 может выдерживать только миллисекундную точность округленными до приращений .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 с TimeStamp, но не смог даже получить отображение, работающее, когда вставка не указала:

Невозможно вставить явное значение в столбец временной метки. Используйте INSERT со списком столбцов, чтобы исключить столбец временной метки или вставить DEFAULT в столбец временной метки.

Кто-нибудь знает, как заставить SchemeExport работать с datetime2 ИЛИ как получить сопоставление меток времени для свойства DateTime?

4b9b3361

Ответ 1

На самом деле ссылка NHibernate указывает, что тип DateTime nhibernate будет хранить .NET DateTime как SQL-время, усеченное на втором уровне (без миллисекундной гранулярности)

Таким образом, он предоставляет тип Timestamp NHibernate (type="Timestamp" в сопоставлении), который будет хранить .NET DateTime как SQL DateTime без усечения. Обратите внимание, что тип данных SQL Timestamp не соответствует не и будет прервать его, если в одной таблице содержится более одного столбца Timestamp. Поэтому важно различать атрибуты sql-type и type в сопоставлении NHibernate.

Кроме того, обратите внимание, что если вы работаете с фильтрами, то такое же правило применяется в определении фильтра: если вы укажете параметр DateTime, значение параметра будет усечено без миллисекунд.

Проверьте глава 5.2.2. Основные типы значений, Таблица 5.3 Типы отображения System.ValueType.

Ответ 2

Для тех, кто хочет фактически сохранить наносекундную часть даты, вам придется использовать DateTime2 как тип sql-столбца, так и тип 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();

Используя это, вы сможете сохранить наносекунды при хранении ваших данных.

Ответ 3

Я столкнулся с той же проблемой с полем CreateDate audit в моих бизнес-классах. Я работал над этим, установив время, используя значение из метода утилиты. Надеюсь, это поможет.

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

Ответ 4

В моем домене допустимо потерять миллисекунды из datetime в 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));
    }
}

Ответ 5

Я смог получить оптимистичную блокировку, используя приведенное ниже: (используя datetime2).

Примечание. Я использовал имя (и случай типа данных) здесь: 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, Object oldVersion, Object obj, SqlCommandInfo sql, сеанс ISessionImplementor)  в NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(Object id, Object [] fields, Object [] oldFields, Object rowId, Boolean [] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, сеанс ISessionImplementor)  в NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object [] fields, Int32 [] dirtyFields, Boolean hasDirtyCollection, Object [] oldFields, Object oldVersion, Object obj, Object 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()