NHibernate 不断丢失 PostgreSQL 9.0 中 DateTime 的时区
我需要说服 NHibernate 存储我所保留的 DateTime 值的时区。这些列是带有时区的时间戳。
我们有一个约定,即我们只将 UTC 日期时间存储在数据库中,因此我们使用没有时区的时间戳
,但我需要数据库来表示知道时区,以便直接访问数据库的报告能够才能正确执行。
我尝试告诉 NHibernate 我有一个 DateTime
的自定义类型,如下所示:
[Serializable]
public class UtcDateTimeUserType<T> : IUserType
{
private static readonly SqlType[] MySqlTypes = {SqlTypeFactory.DateTime };
public SqlType[] SqlTypes
{
get { return MySqlTypes; }
}
public System.Type ReturnedType
{
get { return typeof(T); }
}
public bool IsMutable
{
get { return false; }
}
public object DeepCopy(object value)
{
return value; // DateTime is immutable
}
public new bool Equals(object x, object y)
{
return object.Equals(x, y);
}
public object NullSafeGet(IDataReader resultSet,
string[] names,
object owner)
{
int index0 = resultSet.GetOrdinal(names[0]);
if (resultSet.IsDBNull(index0))
{
return null;
}
var value = resultSet.GetDateTime(index0);
return DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
public void NullSafeSet(IDbCommand statement,
object value,
int index)
{
if (value == null)
{
((IDbDataParameter)statement.Parameters[index]).Value = DBNull.Value;
}
else
{
var dateTime = (DateTime)value;
if (dateTime == DateTime.MinValue) dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
if (dateTime.Kind == DateTimeKind.Unspecified)
throw new InvalidOperationException(
"Attempting to save a datetime without a Kind set.. what time zone are you in?");
((IDbDataParameter) statement.Parameters[index]).Value = dateTime.ToUniversalTime();
}
}
public object Disassemble(object value)
{
return value;
}
public object Assemble(object cached, Object owner)
{
return cached;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public int GetHashCode(Object x)
{
return x.GetHashCode();
}
}
public class NullableUtcDateTimeUserTypeConvention
: UserTypeConvention<UtcDateTimeUserType<DateTime?>>
{
}
public class UtcDateTimeUserTypeConvention
: UserTypeConvention<UtcDateTimeUserType<DateTime>>
{
}
Fluent NHibernateConfiguration
autoPersistenceModel = autoPersistenceModel.Conventions.Setup(s =>
{
s.Add<TableNameConvention>();
s.Add<PrimaryKeyConvention>();
s.Add<CustomForeignKeyConvention>();
s.Add<CascadeConvention>();
s.Add<CalculatedFieldPropertyConvention>();
s.Add<UtcDateTimeUserTypeConvention>();
s.Add<NullableUtcDateTimeUserTypeConvention>();
s.AddAssembly(assembly);
});
这会捕获尝试在未设置时区的情况下保留日期时间的代码。它还确保在进入数据库之前所有 DateTime
都是 UTC,并在返回时将它们正确设置为 UTC。
但下面的语句不起作用:
((IDbDataParameter) statement.Parameters[index]).Value = dateTime.ToUniversalTime();
分配后这仍然是Unspecified
:
((DateTime)((IDbDataParameter) statement.Parameters[index]).Value).Kind
我从NHibernate的代码中注意到PostgresDialect具有以下内容:
RegisterColumnType(DbType.DateTime, "timestamp");
但没有提到timestamp with time zone
或<代码>时间戳。
如何让 NHibernate 将 DateTimes 及其时区持久保存到 Postgres9 中?
I need to convince NHibernate to store the timezone of the DateTime
values I am persisting. The columns are timestamp with time zone
.
We have a convention that we only store UTC DateTimes in the database, so we were using timestamp without time zone
but I needed the database to represent know the timezone so a report that access the database directly would be able to perform correctly.
I've tried telling NHibernate I have a custom type for DateTime
as follows:
[Serializable]
public class UtcDateTimeUserType<T> : IUserType
{
private static readonly SqlType[] MySqlTypes = {SqlTypeFactory.DateTime };
public SqlType[] SqlTypes
{
get { return MySqlTypes; }
}
public System.Type ReturnedType
{
get { return typeof(T); }
}
public bool IsMutable
{
get { return false; }
}
public object DeepCopy(object value)
{
return value; // DateTime is immutable
}
public new bool Equals(object x, object y)
{
return object.Equals(x, y);
}
public object NullSafeGet(IDataReader resultSet,
string[] names,
object owner)
{
int index0 = resultSet.GetOrdinal(names[0]);
if (resultSet.IsDBNull(index0))
{
return null;
}
var value = resultSet.GetDateTime(index0);
return DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
public void NullSafeSet(IDbCommand statement,
object value,
int index)
{
if (value == null)
{
((IDbDataParameter)statement.Parameters[index]).Value = DBNull.Value;
}
else
{
var dateTime = (DateTime)value;
if (dateTime == DateTime.MinValue) dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
if (dateTime.Kind == DateTimeKind.Unspecified)
throw new InvalidOperationException(
"Attempting to save a datetime without a Kind set.. what time zone are you in?");
((IDbDataParameter) statement.Parameters[index]).Value = dateTime.ToUniversalTime();
}
}
public object Disassemble(object value)
{
return value;
}
public object Assemble(object cached, Object owner)
{
return cached;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public int GetHashCode(Object x)
{
return x.GetHashCode();
}
}
public class NullableUtcDateTimeUserTypeConvention
: UserTypeConvention<UtcDateTimeUserType<DateTime?>>
{
}
public class UtcDateTimeUserTypeConvention
: UserTypeConvention<UtcDateTimeUserType<DateTime>>
{
}
Fluent NHibernateConfiguration
autoPersistenceModel = autoPersistenceModel.Conventions.Setup(s =>
{
s.Add<TableNameConvention>();
s.Add<PrimaryKeyConvention>();
s.Add<CustomForeignKeyConvention>();
s.Add<CascadeConvention>();
s.Add<CalculatedFieldPropertyConvention>();
s.Add<UtcDateTimeUserTypeConvention>();
s.Add<NullableUtcDateTimeUserTypeConvention>();
s.AddAssembly(assembly);
});
This catches code trying to persist datetimes without The Timezone set. It also ensures all DateTime
s are UTC before going into the database, and correctly sets them to UTC on the way back out.
But the following statement does not work:
((IDbDataParameter) statement.Parameters[index]).Value = dateTime.ToUniversalTime();
This is still Unspecified
after assignment:
((DateTime)((IDbDataParameter) statement.Parameters[index]).Value).Kind
I notice from NHibernate's code that the PostgresDialect has the following:
RegisterColumnType(DbType.DateTime, "timestamp");
but nothing mentions timestamp with time zone
or timestamptz
.
How to I make NHibernate persist DateTimes with their time zone into Postgres9?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
看起来 NpgSQL 驱动程序有一个错误:
赋值
((IDbDataParameter) statements.Parameters[index]).Value = dateTime.ToUniversalTime();
调用set
值属性。这会将DateTime
转换为NpgsqlTimeStamp
并存储它,然后将其转换回System.DatetTime
并将其存储在值中。问题是它使用的是
NpgsqlTimeStamp
而不是NpgsqlTimeStampTZ
,因此它丢失了时区信息。It looks like the NpgSQL driver has a bug:
The assignment
((IDbDataParameter) statement.Parameters[index]).Value = dateTime.ToUniversalTime();
calls theset
on the Value property. This converts theDateTime
to aNpgsqlTimeStamp
and stores it, then converts that back to aSystem.DatetTime
and stores that in the value.The problem is that it's using
NpgsqlTimeStamp
notNpgsqlTimeStampTZ
, so it loses the time zone information.