Precisión de DateTime en NHibernate y soporte para DateTime2 en NHibernate SchemeExport
fluent-nhibernate timestamp (5)
Me encontré con el mismo problema con un campo de auditoría CreatedDate en mis clases de negocios. Lo solucioné estableciendo el tiempo usando el valor de un método de utilidad. Espero que esto ayude.
/// <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;
}
Luego uso Fluent NHibernate y su característica de automatización para mapear la siguiente clase de POCO simplificada:
public class Foo
{
public virtual int Id { get; set; }
public virtual datetime CreatedDateTime { get; set; }
}
El campo CreatedDateTime se correlacionará con un SQL DateTime de forma predeterminada. Sin embargo, si realizo una prueba para verificar que la entidad se está creando correctamente, falla. Esto se debe a que la precisión del campo DateTime no se mantiene a través de la base de datos SQL. Resumo la razón detrás de esto para ser que un DateTime de MS SQL Server solo puede contener una precisión de milisegundos redondeado a incrementos de .000, .003 o .007 (vea http://msdn.microsoft.com/en-us/library /ms187819.aspx ). Por esta razón, NHibernate trunca los milisegundos al guardar en la tienda. Esto da como resultado que mi prueba falle al verificar que los campos donde persistió correctamente como .NET DateTime tiene sus milisegundos pero el DateTime recuperado después del guardado ha perdido sus milisegundos y, por lo tanto, los dos no son exactamente iguales.
Para superar este problema, he agregado la siguiente asignación al objeto Foo:
public class FooMap : IAutoMappingOverride<Foo>
{
public void Override(AutoMapping<Foo> mapping)
{
mapping.Map(f => f.CreatedDateTime).CustomType("datetime2");
}
}
Entiendo que este mapeo hace que NHibernate mantenga el CreatedDateTime en un tipo SQL de datetime2, que puede almacenar la precisión total que puede tener un .NET DateTime. Esto funciona como un regalo y la prueba ahora pasa.
Sin embargo, con un pase viene otro error: mi prueba que verifica la exportación del esquema ahora falla con el siguiente error:
System.ArgumentException : Dialect does not support DbType.DateTime2
Parameter name: typecode
con un rastro de pila de:
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)
El código usa el objeto NHibernate.Tool.hbm2ddl.SchemaExport para llamar al método Execute.
Estoy usando Fluent v1 y NHibernate v2.1.
También intenté asignar mi DateTime
a TimeStamp, pero ni siquiera pude hacer que la asignación funcionara, ya que la inserción falla al afirmar:
No se puede insertar un valor explícito en una columna de marca de tiempo. Use INSERT
con una lista de columnas para excluir la columna de marca de tiempo, o inserte un DEFAULT
en la columna de marca de tiempo.
¿Alguien sabe cómo hacer que SchemeExport trabaje con datetime2
O cómo hacer que funcione la asignación de datetime
y datetime
para una propiedad de datetime
y datetime
?
En mi dominio, es aceptable perder los milisegundos de datetimes en SQL Server. Por lo tanto, permito una tolerancia en mis probadores de persistencia usando este helper estático (implementación 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));
}
}
Pude hacer que mi bloqueo optimista funcionara usando lo siguiente: (usando datetime2).
Tenga en cuenta que utilicé el nombre (y el caso del nombre-tipo-datos) desde aquí: http://msdn.microsoft.com/en-us/library/system.data.dbtype.aspx "DateTime2" está en mi código de mapeo ( en CustomType) y no en el caso del tipo de datos del servidor Sql ("datetime2"). No estoy seguro de si eso hace la diferencia, pero quería señalarlo.
Mapeo fluido:
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; }
}
La columna del servidor Sql se crea en:
[MyTimestamp] [datetime2](7) NOT NULL
Y mis pruebas básicas funcionan y yo (correctamente) recibo una excepción como esta (cuando alguien más ha actualizado la fila)
La fila fue actualizada o eliminada por otra transacción (o la asignación de valores no guardados fue incorrecta): [DogBreed # abcabc1d-abc4-abc9-abcb-abca01140a27]
at NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32 rows, Object id, Int32 tableNumber, IExpectation expectation, IDbCommand statement)
en 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) en 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) en NHibernate.Persister.Entity .AbstractEntityPersister.Update (ID de objeto, campos Object [], Int32 [] dirtyFields, Boolean hasDirtyCollection, Object [] oldFields, Object oldVersion, Object obj, Object rowId, ISessionImplementor sesión) en NHibernate.Action.EntityUpdateAction.Execute () en NHibernate .Engine.ActionQueue.Execute (ejecutable de IExecutable) en NHibernate.Engine.ActionQueue.ExecuteActions (lista IList) en NHibernate.Engine.ActionQueue.ExecuteActions () en NHibernate.Event.Default.AbstractFlushingEventListe ner.PerformExecutions (sesión de IEventSource) en NHibernate.Event.Default.DefaultFlushEventListener.OnFlush (evento FlushEvent) en NHibernate.Impl.SessionImpl.Flush () en NHibernate.Transaction.AdoTransaction.Commit ()
Para cualquiera que quiera mantener la parte de nanosegundos de la fecha, tendrá que usar DateTime2 como el tipo sql-column, así como el tipo Nhibernate DateTime2.
Aquí está mi convención para configurar esto (usando con fluidez)
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
}
}
Y para activar la convención:
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();
Con esto, podrá conservar nanosegundos cuando almacene sus fechas.
En realidad, la referencia NHibernate establece que el tipo DateTime nhibernate almacenará .NET DateTime como una fecha y hora SQL truncado en el segundo nivel (sin granularidad de milisegundos)
Como tal, proporciona el tipo Timestamp
NHibernate ( type="Timestamp"
en el mapeo) que almacenará un .NET DateTime
como un SQL datetime
sin truncamiento. Tenga en cuenta que un tipo de datos SQL timestamp
no es necesario y se romperá de hecho si tiene más de una columna timestamp
en una tabla. Por lo tanto, es importante diferenciar entre los atributos sql-type
y type
en el mapeo NHibernate.
Además, tenga en cuenta que si está trabajando con filtros, la misma regla se aplica a la definición del filtro: si especifica un parámetro DateTime
, el valor del parámetro se truncará sin milisegundos.
Vea el capítulo 5.2.2. Tipos de valores básicos , Tabla 5.3 Tipos de asignación de tipos de System.ValueType .