使用 NHibernate 保存和加载 Utc DateTime
Save and load Utc DateTime with NHibernate
我在将 DateTime 保存到 SQL Lite 数据库时遇到问题。 (也许还有 MS SQL)
我想使用 NHibernate 将 UTC 时间的 DateTime 保存到数据库并从数据库加载它。我们在 hole 应用程序中使用 utc 时间工作,当我们在 ui 上显示时间时,我们将其更改为本地时间。
我阅读了很多关于 DateTime 和 NHibernate 的文章:
- http://jameskovacs.com/2011/01/26/datetime-support-in-nhibernate/
- http://www.milkcarton.com/blog/2007/01/19/NHibernate+DateTime+And+UTC.aspx
- Rehydrating fluent nhibernate configured DateTime as Kind Utc rather than Unspecified
- https://nhibernate.jira.com/browse/NH-2520
但没有任何效果。
一些例子:
PreUpdate: 在保存实体之前。
Saved:是用nhibernate保存后保存的对象(repo.save(o);).
已加载:当我通过 id 从存储库加载实体时。
// The next 3 examples are with:
o.Created = DateTime.UtcNow;
映射类型:CustomType<TimestampType>()
协调时间:16:44...本地时间:18:44
- 更新前:2015-03-30T16:44:35.7636679Z 勾选:635633306757636679 Kind:Utc
- 已保存:2015-03-30T16:44:35.7636679Z 刻度:635633306757636679 Kind:Utc
- 已加载:2015-03-30T18:44:35.7636679 Tick:635633378757636679 Kind:Unspecified
这里的问题是,当我通过 id 重新加载对象时,新对象的时间是 18... (+2h) 而不是 16.... 并且 DateTime 类型是未指定的。
映射类型:CustomType<DateTimeType>()
协调时间:16:49...本地时间:18:49
- 更新前:2015-03-30T16:49:00.2754289Z 勾选:635633309402754289 Kind:Utc
- 已保存:2015-03-30T16:49:00.2754289Z 刻度:635633309402754289 Kind:Utc
- 已加载:2015-03-30T16:49:00.0000000 Tick:635633309400000000 Kind:Unspecified
使用此解决方案,我放宽了毫秒数,并且 DateTime 种类也是未指定的。
映射类型:CustomType<UtcDateTimeType>()
协调时间:17:01...本地时间:19:01
- 更新前:2015-03-30T17:01:32.9663859Z 勾选:635633316929663859 Kind:Utc
- 已保存:2015-03-30T17:01:32.9663859Z 刻度:635633316929663859 Kind:Utc
- 已加载:2015-03-30T19:01:32.0000000Z Tick:635633388920000000 Kind:Utc
通过这个解决方案,我放宽了毫秒数,DateTime 类型是 utc 但它是错误的时间,应该是 17:01...
所以另一个想法是仅在应用程序中使用 DateTime.Now 并将 utc 时间保存在数据库中。一些例子:
// The next 3 examples are with:
o.Created = DateTime.Now;
映射类型:CustomType<TimestampType>()
协调时间:17:21...本地时间:19:21
- PreUpdate: 2015-03-30T19:21:44.7938077+02:00 Tick: 635633401047938077 Kind:Local
- 已保存:2015-03-30T19:21:44.7938077+02:00 刻度:635633401047938077 Kind:Local
- 已加载:2015-03-30T19:21:44.7938077 刻度:635633401047938077 Kind:Unspecified
通过这个解决方案,我得到了毫秒数,DateTime 类型是未指定的,加载时间不是 utc。
映射类型:CustomType<DateTimeType>()
协调时间:17:19...本地时间:19:19
- PreUpdate:2015-03-30T19:19:27.3114047+02:00 Tick:635633399673114047 Kind:Local
- 已保存:2015-03-30T19:19:27.3114047+02:00 刻度:635633399673114047 Kind:Local
- 已加载:2015-03-30T19:19:27.0000000 刻度:635633399670000000 Kind:Unspecified
使用此解决方案,我放宽了毫秒数,DateTime 类型也是未指定的,加载时间不是 utc。
映射类型:CustomType<UtcDateTimeType>()
协调时间:17:14...本地时间:19:14
- PreUpdate:2015-03-30T19:14:31.3030033+02:00 Tick:635633396713030033 Kind:Local
- 已保存:2015-03-30T19:14:31.3030033+02:00 刻度:635633396713030033 Kind:Local
- 已加载:2015-03-30T21:14:31.0000000Z 刻度:635633468710000000 Kind:Utc
通过这个解决方案,我放宽了毫秒数,DateTime 类型是 utc 但它是错误的时间,应该是 17:14...
所以我有一些问题:
- 为什么 NHibernate 加载本地时间但使用类型为 utc(UtcDateTimeType 和 o.Created=DateTime.UtcNow)
- 在孔应用程序中和 UI localtime 中使用 utc 还是在所有地方都使用 localtime 并将时间 utc 保存在数据库中更好。
我还创建了自己的映射:
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() + '\'';
}
}
}
- 将日期时间加载为 UTC 的行为,我相信使用类型
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 方言中可能存在某种错误。
- 我认为保存 UTC 是更好的选择,尤其是当用户处于时区范围内时。但是,如果用户在同一时区,则没有转换点,因为这使得诊断错误特别是查询数据库变得困难,因为您必须始终转换值。
作为精确解决此问题的方法,您可以添加这样的自定义类型,
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();
}
}
感谢您的回答。使用您的自定义类型版本,我在加载日期时间时通常有错误的时间。问题是这段代码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=""/>
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);
}
我遇到了同样的问题,在我的例子中,我试图将所有内容都作为 Utc 往返,字段映射为 UtcDateTimeType
,但返回的时间标记为 UTC,但转换为本地时间。答案既简单又烦人。你必须 tell the SQLite engine not to convert DateTimes for you by adding a parameter to the connection string:
config.DataBaseIntegration(c =>
{
c.Dialect<SQLiteDialect>();
c.Driver<SQLite20Driver>();
c.ConnectionString = "DataSource=MyAppDB.sqlite;DateTimeKind=Utc";
// ^^^^^^^^^^^^^^^^
...
这样做之后,我的应用程序表现得更加明智。
我在将 DateTime 保存到 SQL Lite 数据库时遇到问题。 (也许还有 MS SQL)
我想使用 NHibernate 将 UTC 时间的 DateTime 保存到数据库并从数据库加载它。我们在 hole 应用程序中使用 utc 时间工作,当我们在 ui 上显示时间时,我们将其更改为本地时间。
我阅读了很多关于 DateTime 和 NHibernate 的文章:
- http://jameskovacs.com/2011/01/26/datetime-support-in-nhibernate/
- http://www.milkcarton.com/blog/2007/01/19/NHibernate+DateTime+And+UTC.aspx
- Rehydrating fluent nhibernate configured DateTime as Kind Utc rather than Unspecified
- https://nhibernate.jira.com/browse/NH-2520
但没有任何效果。 一些例子:
PreUpdate: 在保存实体之前。
Saved:是用nhibernate保存后保存的对象(repo.save(o);).
已加载:当我通过 id 从存储库加载实体时。
// The next 3 examples are with:
o.Created = DateTime.UtcNow;
映射类型:CustomType<TimestampType>()
协调时间:16:44...本地时间:18:44
- 更新前:2015-03-30T16:44:35.7636679Z 勾选:635633306757636679 Kind:Utc
- 已保存:2015-03-30T16:44:35.7636679Z 刻度:635633306757636679 Kind:Utc
- 已加载:2015-03-30T18:44:35.7636679 Tick:635633378757636679 Kind:Unspecified
这里的问题是,当我通过 id 重新加载对象时,新对象的时间是 18... (+2h) 而不是 16.... 并且 DateTime 类型是未指定的。
映射类型:CustomType<DateTimeType>()
协调时间:16:49...本地时间:18:49
- 更新前:2015-03-30T16:49:00.2754289Z 勾选:635633309402754289 Kind:Utc
- 已保存:2015-03-30T16:49:00.2754289Z 刻度:635633309402754289 Kind:Utc
- 已加载:2015-03-30T16:49:00.0000000 Tick:635633309400000000 Kind:Unspecified
使用此解决方案,我放宽了毫秒数,并且 DateTime 种类也是未指定的。
映射类型:CustomType<UtcDateTimeType>()
协调时间:17:01...本地时间:19:01
- 更新前:2015-03-30T17:01:32.9663859Z 勾选:635633316929663859 Kind:Utc
- 已保存:2015-03-30T17:01:32.9663859Z 刻度:635633316929663859 Kind:Utc
- 已加载:2015-03-30T19:01:32.0000000Z Tick:635633388920000000 Kind:Utc
通过这个解决方案,我放宽了毫秒数,DateTime 类型是 utc 但它是错误的时间,应该是 17:01...
所以另一个想法是仅在应用程序中使用 DateTime.Now 并将 utc 时间保存在数据库中。一些例子:
// The next 3 examples are with:
o.Created = DateTime.Now;
映射类型:CustomType<TimestampType>()
协调时间:17:21...本地时间:19:21
- PreUpdate: 2015-03-30T19:21:44.7938077+02:00 Tick: 635633401047938077 Kind:Local
- 已保存:2015-03-30T19:21:44.7938077+02:00 刻度:635633401047938077 Kind:Local
- 已加载:2015-03-30T19:21:44.7938077 刻度:635633401047938077 Kind:Unspecified
通过这个解决方案,我得到了毫秒数,DateTime 类型是未指定的,加载时间不是 utc。
映射类型:CustomType<DateTimeType>()
协调时间:17:19...本地时间:19:19
- PreUpdate:2015-03-30T19:19:27.3114047+02:00 Tick:635633399673114047 Kind:Local
- 已保存:2015-03-30T19:19:27.3114047+02:00 刻度:635633399673114047 Kind:Local
- 已加载:2015-03-30T19:19:27.0000000 刻度:635633399670000000 Kind:Unspecified
使用此解决方案,我放宽了毫秒数,DateTime 类型也是未指定的,加载时间不是 utc。
映射类型:CustomType<UtcDateTimeType>()
协调时间:17:14...本地时间:19:14
- PreUpdate:2015-03-30T19:14:31.3030033+02:00 Tick:635633396713030033 Kind:Local
- 已保存:2015-03-30T19:14:31.3030033+02:00 刻度:635633396713030033 Kind:Local
- 已加载:2015-03-30T21:14:31.0000000Z 刻度:635633468710000000 Kind:Utc
通过这个解决方案,我放宽了毫秒数,DateTime 类型是 utc 但它是错误的时间,应该是 17:14...
所以我有一些问题:
- 为什么 NHibernate 加载本地时间但使用类型为 utc(UtcDateTimeType 和 o.Created=DateTime.UtcNow)
- 在孔应用程序中和 UI localtime 中使用 utc 还是在所有地方都使用 localtime 并将时间 utc 保存在数据库中更好。
我还创建了自己的映射:
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() + '\'';
}
}
}
- 将日期时间加载为 UTC 的行为,我相信使用类型
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 方言中可能存在某种错误。
- 我认为保存 UTC 是更好的选择,尤其是当用户处于时区范围内时。但是,如果用户在同一时区,则没有转换点,因为这使得诊断错误特别是查询数据库变得困难,因为您必须始终转换值。
作为精确解决此问题的方法,您可以添加这样的自定义类型,
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();
}
}
感谢您的回答。使用您的自定义类型版本,我在加载日期时间时通常有错误的时间。问题是这段代码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=""/>
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);
}
我遇到了同样的问题,在我的例子中,我试图将所有内容都作为 Utc 往返,字段映射为 UtcDateTimeType
,但返回的时间标记为 UTC,但转换为本地时间。答案既简单又烦人。你必须 tell the SQLite engine not to convert DateTimes for you by adding a parameter to the connection string:
config.DataBaseIntegration(c =>
{
c.Dialect<SQLiteDialect>();
c.Driver<SQLite20Driver>();
c.ConnectionString = "DataSource=MyAppDB.sqlite;DateTimeKind=Utc";
// ^^^^^^^^^^^^^^^^
...
这样做之后,我的应用程序表现得更加明智。