Точность DateTime в NHibernate и поддержка DateTime2 в NHibernate SchemeExport - PullRequest
23 голосов
/ 09 января 2010

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

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

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

Ответы [ 5 ]

32 голосов
/ 23 февраля 2010

На самом деле ссылка NHibernate гласит, что тип Datehime nhibernate будет хранить .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 .

5 голосов
/ 10 июня 2015

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

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

1 голос
/ 09 января 2010

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

Мне удалось отработать мою оптимистическую блокировку, используя следующее: (используя 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 (поля объекта, поля объекта [], объекта [] oldFields, объекта rowId,Boolean [] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, сеанс ISessionImplementor) в NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert (идентификатор объекта, Object [] поля, Object [] oldFields, Object rowId, BooincludeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, сеанс ISessionImplementor) в NHibernate.Persister.Entity.AbstractEntityPersister.Update (идентификатор объекта, поля Object [], Int32 [] dirtyFields, логическое значение hasDirtyCollection, Object [] 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. Событие. Событие. Событие. СобытиеDefaultFlushEventListener.OnFlush (событие FlushEvent) в NHibernate.Impl.SessionImpl.Flush () в NHibernate.Transaction.AdoTransaction.Commit ()

0 голосов
/ 23 февраля 2010

В моем домене допустимо потерять миллисекунды с датами в 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));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...