我将DateTime保存到SQL Lite数据库时遇到问题。 (也许还有MS SQL)
我希望将NHibernate的UTCTime时间保存到数据库并从数据库加载它。我们使用utc时间在孔应用程序中工作,当我们在ui上显示时间时,我们将其更改为当地时间。
我读了很多关于DateTime和NHibernate的信息:
但没有任何作用。 一些例子:
PreUpdate :在保存实体之前。
已保存:是使用nhibernate(repo.save(o);)保存后保存的对象。
已加载:当我通过ID从存储库加载实体时。
// The next 3 examples are with:
o.Created = DateTime.UtcNow;
CustomType<TimestampType>()
UtcTime:16:44 ......当地时间:18:44
这里的问题是,当我通过id重新加载对象时,新对象的时间为18 ...(+ 2h)而不是16 ....而DateTime种类是未指定的。
CustomType<DateTimeType>()
UtcTime:16:49 ...当地时间:18:49
使用此解决方案,我会丢失毫秒,而DateTime类型也是未指定的。
CustomType<UtcDateTimeType>()
UtcTime:17:01 ... LocalTime:19:01
使用这个解决方案,我松开了毫秒,DateTime类是utc,但它是错误的时间,它应该是17:01 ....
所以另一个想法是在应用程序中仅使用DateTime.Now并将utc时间保存在数据库中。一些例子:
// The next 3 examples are with:
o.Created = DateTime.Now;
CustomType<TimestampType>()
UtcTime:17:21 ......当地时间:19:21
使用此解决方案,我有毫秒,DateTime类型未指定,加载时间不是utc。
CustomType<DateTimeType>()
UtcTime:17:19 ...当地时间:19:19
使用此解决方案,我会丢失毫秒,DateTime类型也是未指定的,加载时间不是utc。
CustomType<UtcDateTimeType>()
UtcTime:17:14 ......当地时间:19:14
使用这个解决方案,我放松了毫秒,DateTime种类是utc,但这是错误的时间,它应该是17:14 ....
所以我有一些问题:
我还创建了一个自己的映射:
namespace Persistence.Common.NHibernate
{
using System;
using System.Data;
using global::NHibernate.Engine;
using global::NHibernate.Type;
/// <summary>
/// This is almost the exact same type as the DateTime except it can be used
/// in the version column, stores it to the accuracy the database supports,
/// and will default to the value of DateTime.Now if the value is null.
/// </summary>
/// <remarks>
/// <p>
/// The value stored in the database depends on what your data provider is capable
/// of storing. So there is a possibility that the DateTime you save will not be
/// the same DateTime you get back when you check DateTime.Equals(DateTime) because
/// they will have their milliseconds off.
/// </p>
/// <p>
/// For example - SQL Server 2000 is only accurate to 3.33 milliseconds. So if
/// NHibernate writes a value of <c>01/01/98 23:59:59.995</c> to the Prepared Command, MsSql
/// will store it as <c>1998-01-01 23:59:59.997</c>.
/// </p>
/// <p>
/// Please review the documentation of your Database server.
/// </p>
/// </remarks>
[Serializable]
public class CustomUtcTimestampType : TimestampType
{
public CustomUtcTimestampType()
{
}
public override object Get(IDataReader rs, int index)
{
return Convert.ToDateTime(rs[index]).ToLocalTime();
}
/// <summary>
/// Sets the value of this Type in the IDbCommand.
/// </summary>
/// <param name="st">The IDbCommand to add the Type's value to.</param>
/// <param name="value">The value of the Type.</param>
/// <param name="index">The index of the IDataParameter in the IDbCommand.</param>
/// <remarks>
/// No null values will be written to the IDbCommand for this Type.
/// </remarks>
public override void Set(IDbCommand st, object value, int index)
{
DateTime dateTime = (DateTime)((value is DateTime) ? value : DateTime.UtcNow);
dateTime = DateTime.SpecifyKind(dateTime.ToUniversalTime(), DateTimeKind.Unspecified);
((IDataParameter)st.Parameters[index]).Value = dateTime;
}
public override string Name
{
get { return "CustomUtcTimestamp"; }
}
public override object FromStringValue(string xml)
{
return DateTime.Parse(xml);
}
#region IVersionType Members
public override object Seed(ISessionImplementor session)
{
if (session == null)
{
return DateTime.UtcNow;
}
return Round(DateTime.UtcNow, session.Factory.Dialect.TimestampResolutionInTicks);
}
#endregion
public object StringToObject(string xml)
{
return DateTime.Parse(xml);
}
public override string ObjectToSQLString(object value, global::NHibernate.Dialect.Dialect dialect)
{
return '\'' + value.ToString() + '\'';
}
}
}
答案 0 :(得分:3)
UtcDateTimeType
时的预期行为是假设时间设置为UTC,并且当获取它时也将其视为UTC,如果您执行快速测试,
public class UtcTime
{
public virtual long Id { get; set; }
public virtual DateTime DateSaved { get; set; }
}
public class UtcTimeClassMap : ClassMap<UtcTime>
{
public UtcTimeClassMap()
{
Id(t => t.Id).GeneratedBy.Native();
Map(t => t.DateSaved ).CustomType<UtcDateTimeType>();
}
}
[Test]
public void SimpleTest()
{
long id = 0;
ISession session = _sessionFactory.OpenSession();
using (ITransaction tran = session.BeginTransaction())
{
UtcTime utc = new UtcTime();
utc.DateSaved = DateTime.Now;
session.Save(utc);
tran.Commit();
Console.WriteLine(utc.DateSaved.Ticks + "_" + utc.DateSaved.Kind + "_" + utc.Date.ToString());
id = utc.Id;
}
session.Flush();
session.Clear();
session = _sessionFactory.OpenSession();
var retrieved = session.Get<UtcTime>(id);
Console.WriteLine(retrieved.DateSaved.Ticks + "_" + retrieved.Date.Kind + "_" + retrieved.DateSaved.ToString());
}
<强>输出强>
INSERT INTO [UtcTime] (DateSaved) VALUES (?); select SCOPE_IDENTITY()
635634005813892469_Local_31/03/2015 12:09:41 PM
SELECT utctime0_.Id as Id3_0_, utctime0_.DateSaved as Date3_0_ FROM [UtcTime] utctime0_ WHERE utctime0_.Id=?
635634005810000000_Utc_31/03/2015 12:09:41 PM
即使我将12:09:41保持为本地,当我取回它的UTC时,同时假设一切都在UTC中发生。此测试是使用SQLServer数据库完成的。
如果我用SQLite重复相同的测试,输出是,
INSERT INTO "UtcTime" (DateSaved) VALUES (?); select last_insert_rowid()
635634005197863939_Local_31/03/2015 12:08:39 PM
SELECT utctime0_.Id as Id3_0_, utctime0_.DateSaved as Date3_0_ FROM "UtcTime" utctime0_ WHERE utctime0_.Id=?
635634401190000000_Utc_31/03/2015 11:08:39 PM
我可以在这里看到一小时无法解释的差异(因为当地时间和进行测试的UTC之间的差异是11小时,这不能解释它。 我能想到的唯一解释是SQLLite方言中可能存在某种错误。
作为精确解决此问题的方法,您可以添加像这样的自定义类型
public class UTCTimeStampType : TimestampType
{
public override object Get(IDataReader rs, int index)
{
return ConvertToUtc(base.Get(rs, index));
}
public override object Get(IDataReader rs, string name)
{
return ConvertToUtc(base.Get(rs, name));
}
public override object FromStringValue(string xml)
{
return ConvertToUtc(base.FromStringValue(xml));
}
private DateTime ConvertToUtc(object value)
{
var dateTime = (DateTime) value;
return new DateTime(dateTime.Ticks).ToUniversalTime();
}
}
答案 1 :(得分:2)
感谢您的回答。使用您的自定义类型版本时,我通常在加载日期时间时出错。问题是这段代码base.Get(rs, index)
。当我执行此方法时,它将utc时间从数据库转换为错误的DateTime。我现在的解决方案是在utc中保存时间,但在将DateTimeKind更改为Unspecified之前将值存储在数据库中(SQLite)。结果是最后没有Z的时间。当我加载时间时,我在加载DateTime后更改了DateTimeKind,一切正常。
以下代码解决了我的问题:
using System;
using System.Data;
using global::NHibernate.Engine;
using global::NHibernate.Type;
using Foundation.Core;
/// <summary>
/// This type save the <see cref="DateTime"/> to the database. You need to save the <see cref="DateTime"/> in UTC (<see cref="DateTimeKind.Utc"/>).
/// When you load the <see cref="DateTime"/>, then time is in UTC.
/// </summary>
/// <seealso cref="http://stackoverflow.com/questions/29352719/save-and-load-utc-datetime-with-nhibernate"/>
public class UtcTimestampType : TimestampType
{
public override string Name
{
get { return "UtcTimestamp"; }
}
/// <summary>
/// Sets the value of this Type in the IDbCommand.
/// </summary>
/// <param name="st">The IDbCommand to add the Type's value to.</param>
/// <param name="value">The value of the Type.</param>
/// <param name="index">The index of the IDataParameter in the IDbCommand.</param>
/// <remarks>
/// No null values will be written to the IDbCommand for this Type.
/// The <see cref="DateTime.Kind"/> must be <see cref="DateTimeKind.Utc"/>.
/// </remarks>
public override void Set(IDbCommand st, object value, int index)
{
DateTime dateTime = (DateTime)((value is DateTime) ? value : DateTime.UtcNow);
Check.IsValid(() => dateTime, dateTime, time => time.Kind == DateTimeKind.Utc, "You need to save the date time in the utc format.");
// Change the kind to unspecified, because when we load the datetime we have wrong values with kind utc.
((IDataParameter)st.Parameters[index]).Value = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
}
public override object Get(IDataReader rs, int index)
{
return ChangeDateTimeKindToUtc(base.Get(rs, index));
}
public override object Get(IDataReader rs, string name)
{
return ChangeDateTimeKindToUtc(base.Get(rs, name));
}
public override object FromStringValue(string xml)
{
return ChangeDateTimeKindToUtc(base.FromStringValue(xml));
}
public override object Seed(ISessionImplementor session)
{
if (session == null)
{
return DateTime.UtcNow;
}
return Round(DateTime.UtcNow, session.Factory.Dialect.TimestampResolutionInTicks);
}
private DateTime ChangeDateTimeKindToUtc(object value)
{
DateTime dateTime = (DateTime)value;
return new DateTime(dateTime.Ticks, DateTimeKind.Utc);
}
}
您如何看待这两种方法?我需要这些吗?我应该使用Round(DateTime.UtcNow...
吗?何时需要FromStringValue
?
public override object FromStringValue(string xml)
{
return ChangeDateTimeKindToUtc(base.FromStringValue(xml));
}
public override object Seed(ISessionImplementor session)
{
if (session == null)
{
return DateTime.UtcNow;
}
return Round(DateTime.UtcNow, session.Factory.Dialect.TimestampResolutionInTicks);
}
答案 2 :(得分:0)
我遇到了同样的问题,在我的情况下,我试图将所有内容都映射为try/catch
,以Utc的形式来回传递,但是时间又回到了UTC,但转换为本地时间。答案既简单又烦人。您必须tell the SQLite engine not to convert DateTimes for you by adding a parameter to the connection string:
UtcDateTimeType
完成此操作后,我的应用程序的行为更加明智。