Точность 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. Я недооцениваю причину этого в том, что DateTime 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 имущество?

5 ответов

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

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

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

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

Любой, кто хочет сохранить наносекундную часть даты, должен использовать DateTime2 в качестве типа столбца sql, а также тип Datehime2 Nhibernate.

Вот мое соглашение для настройки этого (используя свободное владение)

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

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

Я столкнулся с той же проблемой с полем аудита 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;
    }

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

Обратите внимание, что я использовал имя (и регистр имени типа данных) отсюда: http://msdn.microsoft.com/en-us/library/system.data.dbtype.aspx"DateTime2" находится в моем коде отображения (под CustomType), а не регистр типа данных сервера Sql ("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(идентификатор объекта, поля объекта [], объект [] oldFields, объект rowId, логический [] includeProperty, Int32 j, объект oldVersion, объектный объект, SqlCommandInfo sql, сеанс ISessionImplementor) в NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(Идентификатор объекта, поля Object[], Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, сеанс ISessionImplementor) в NHibernate.Pister..AbstractEntityPersister.Update (идентификатор объекта, поля объекта [], Int32[] dirtyFields, логическое значение hasDirtyCollection, объект [] oldFields, объект oldVersion, объект obj, объект rowId, сеанс ISessionImplementor) в NHibernate.Action.EntityUpdateAction.Exiberte (at).Engine.ActionQueue.Execute (исполняемый файл IExecutable) в NHibernate.Engine.ActionQueue.ExecuteActions(список IList) в NHibernate.Engine.ActionQueue.ExecuteActions() в NHibernate.Event.Default.AbstractFlushingEventListe ner.PerformExecutions (сеанс IEventSource) в NHibernate.Event.Default.DefaultFlushEventListener.OnFlush (событие FlushEvent) в NHibernate.Impl.SessionImpl.Flush() в NHibernate.Transaction.AdoTransaction.Commit()

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