NHibernate 不断丢失 PostgreSQL 9.0 中 DateTime 的时区

发布于 2024-12-05 04:11:23 字数 4359 浏览 1 评论 0原文

我需要说服 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 DateTimes 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

心凉怎暖 2024-12-12 04:11:23

看起来 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 the set on the Value property. This converts the DateTime to a NpgsqlTimeStamp and stores it, then converts that back to a System.DatetTime and stores that in the value.

The problem is that it's using NpgsqlTimeStamp not NpgsqlTimeStampTZ, so it loses the time zone information.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文