Précision DateTime dans le NHibernate et prise en charge du DateTime2 dans le NHibernate Scheme Export
j'utilise alors Fluent NHibernate et sa fonction d'automatisation pour mapper la classe de POCO simplifiée suivante:
public class Foo
{
public virtual int Id { get; set; }
public virtual datetime CreatedDateTime { get; set; }
}
le champ CreatedDateTime correspondra par défaut à une DateTime SQL. Cependant si je fais un test pour vérifier que l'entité est créée correctement, il échoue. Ceci est dû au fait que la précision du champ DateTime n'est pas maintenue dans la base de données SQL. J'en déduis que la raison en est qu'un serveur MS SQL DateTime ne peut contenir que de la milliseconde. précision arrondie par incréments de .000, .003, ou .007 (voir http://msdn.microsoft.com/en-us/library/ms187819.aspx). Pour cette raison, NHibernate tronque les millisecondes en économisant sur le magasin. Il en résulte que mon test échoue lorsque je vérifie que les champs où a persisté correctement mon.net DateTime contient ses millisecondes mais la DateTime récupérée après la sauvegarde a perdu ses millisecondes et donc les deux ne sont pas vraiment égaux.
pour surmonter cela problème, j'ai ajouté le mappage suivant à l'objet Foo:
public class FooMap : IAutoMappingOverride<Foo>
{
public void Override(AutoMapping<Foo> mapping)
{
mapping.Map(f => f.CreatedDateTime).CustomType("datetime2");
}
}
je comprends que cette cartographie fait NHibernate persister le CreatedDateTime à un type SQL de datetime2, qui peut stocker toute la précision qu'un .NET DateTime peut. Cela fonctionne un régal et le test passe maintenant.
cependant avec un succès vient un autre échec: mon test qui vérifie l'exportation du schéma échoue maintenant avec l'erreur suivante:
System.ArgumentException : Dialect does not support DbType.DateTime2
Parameter name: typecode
avec une trace de pile 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)
le code utilise le NHibernate.Outil.hbm2ddl.Objet SchemaExport pour appeler la méthode Execute.
J'utilise Fluent v1 et NHibernate v2.1.
j'ai aussi essayé de cartographier mon DateTime
pour un TimeStamp, mais ne pouvait même pas obtenir la cartographie de travail que l'insertion échoue en déclarant:
impossible d'insérer une valeur explicite dans une colonne timestamp. Utilisez INSERT
avec une liste de colonnes pour exclure la colonne timestamp, ou insérez un DEFAULT
dans le colonne de type timestamp.
est-ce que quelqu'un sait comment faire fonctionner le schéma exportation avec un datetime2
OU comment obtenir le timestamp de la cartographie de travail pour un datetime
propriété?
5 réponses
en fait la référence NHibernate indique que le type DateTime nhibernate stockera le .net DateTime comme un SQL datetime tronqué au second niveau (Pas de granularité milliseconde)
en tant que tel il fournit le Timestamp
NHibernate type (type="Timestamp"
dans le mapping) qui stockera un .NET datetime sans troncature. Notez ici qu'un SQL timestamp
type de données est nécessaire et permettra en effet de se briser si vous avez plus d'un timestamp
colonne en un table. Il est donc important de faire la différence entre les sql-type
et type
attributs dans le NHibernate de cartographie.
en Outre, notez que si vous travaillez avec des filtres, la même règle s'applique à la définition du filtre: Si vous spécifiez un DateTime
paramètre, la valeur du paramètre sera tronquée sans millisecondes.
découvrez chapitre 5.2.2. Types de valeurs de base,Tableau 5.3 Système.Types De Cartographie ValueType.
pour quiconque cherche à conserver la partie nanoseconde de la date, vous devrez utiliser DateTime2 comme type de colonne sql ainsi que le type datetime2 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
}
}
Et pour activer la 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();
en utilisant ceci, vous pourrez conserver des nanosecondes lors du stockage de vos datetimes.
j'ai rencontré le même problème avec un champ de vérification CreatedDate sur mes classes d'affaires. J'ai travaillé autour de lui en définissant l'heure en utilisant la valeur d'une méthode utilitaire. Espérons que cette aide.
/// <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;
}
dans mon domaine, il est acceptable de perdre les millisecondes de datetimes dans SQL Server. J'autorise donc une tolérance dans mes testeurs de persistance en utilisant cette aide statique (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));
}
}
j'ai pu obtenir mon verrouillage optimiste travaillé en utilisant le ci-dessous: (en utilisant datetime2).
Note, j'ai utilisé le nom (et le cas du type de données-nom) d'ici: http://msdn.microsoft.com/en-us/library/system.data.dbtype.aspx "DateTime2"est dans mon code de mapping (sous CustomType) et non dans le cas du type de données Sql Server ("datetime2"). Je ne suis pas sûr que cela fasse une différence, mais je voulais le souligner.
Cartographie Fluente:
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; }
}
le La colonne Sql Server est créée à:
[MyTimestamp] [datetime2](7) NOT NULL
Et mes tests de base de travail et j'ai (bien) recevoir une exception comme ça (quand quelqu'un d'autre a updted la ligne)
Ligne a été mis à jour ou supprimé par une autre transaction (ou unsaved-value mapping a été incorrect): [DogBreed#abcabc1d-abc4-abc9-abcb-abca01140a27]
at NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32 rows, Object id, Int32 tableNumber, IExpectation expectation, IDbCommand statement)
à NHibernate.Persister.Entité.AbstractEntityPersister.Mise à jour(id de l'Objet, Object[] champs, Object[] oldFields, Objet rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session) à NHibernate.Persister.Entité.AbstractEntityPersister.UpdateOrInsert (Object id, Object [] fields, Object [] oldFields, Object rowId, Boolean [] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session) à NHibernate.Persister.Entité.AbstractEntityPersister.Mise à jour(id de l'Objet, Object[] champs, Int32[] dirtyFields, Boolean hasDirtyCollection, Object[] oldFields, Objet oldVersion, Object obj, Objet rowId, ISessionImplementor session) à NHibernate.Action.EntityUpdateAction.Exécuter() à NHibernate.Moteur.ActionQueue.Exécutable (exécutable exécutable exécutable)) à NHibernate.Moteur.ActionQueue.ExecuteActions(IList liste) à NHibernate.Moteur.ActionQueue.ExecuteActions() à NHibernate.Événement.Défaut.AbstractFlushingEventListener.Performexécutions (ieventsource session) à NHibernate.Événement.Défaut.DefaultFlushEventListener.OnFlush(FlushEvent événement) à NHibernate.Impl.SessionImpl.Flush() à NHibernate.Transaction.AdoTransaction.Commit ()