Net7 EF Core 使用Postgresql插入数据时间戳错误

TerraMours框架踩坑记录

EF Core 插入数据的时候,时间戳问题

错误信息:

InvalidCastException: Cannot write DateTime with Kind=Local to PostgreSQL type 'timestamp with time zone', 
only UTC is supported. Note that it's not possible to mix DateTimes with different Kinds in an array/range. 
See the Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy behavior.

image-20230510223827093

解决方案

在Program里面添加如下代码

//用于启用或禁用 Npgsql 客户端与 Postgres 服务器之间的时间戳行为。它并不会直接修改 Postgres 的时区设置。
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);

其实错误信息已经告诉我们了“AppContext switch to enable legacy behavior.”

这样存储时间的格式是:2023-05-11 06:52:35.151 +0800 会带上我们的时区偏移量 UTC+8

相关问题GPT回答:

这两个配置项的作用都与 PostgreSQL 数据库及其驱动程序 Npgsql 有关。

Npgsql.EnableLegacyTimestampBehavior
这个配置项用于启用 Npgsql 驱动程序的旧版时间戳行为。在新版 Npgsql 驱动程序中,时间戳类型(即 PostgreSQL 中的 timestamp、timestamptz 类型)会与 .NET 中的 DateTime 或 DateTimeOffset 类型进行自动转换。但是,在某些情况下,这种自动转换可能会导致一些精度上的损失,例如,在 PostgreSQL 中,时间戳可以精确到微秒级别,而在 .NET 中,DateTime 只能精确到毫秒级别,这就可能导致数据精度丢失。

为了解决这个问题,Npgsql 从 5.0 版本开始默认启用新版时间戳行为。如果你希望启用旧版时间戳行为,可以手动调用 AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true) 方法来启用。启用旧版时间戳行为之后,时间戳类型将不会自动转换为 .NET 中的 DateTime 或 DateTimeOffset 类型,而是作为 Npgsql 中自定义的 NpgsqlDateTime 类型进行处理。这样可以保持时间戳类型的精度,同时也可能带来一些其他方面的问题。因此,在启用旧版时间戳行为之前,一定要确认自己的代码是否需要这个功能,以及是否能够处理好精度问题。

Npgsql.DisableDateTimeInfinityConversions
这个配置项用于禁用 Npgsql 驱动程序中的时间值无限大(infinity)自动转换功能。在 PostgreSQL 中,timestamp 和 timestamptz 类型中可以存储时间值无限大(infinity)和时间值负无穷大(negative infinity)。默认情况下,Npgsql 驱动程序会自动将这些时间值无限大转换为 .NET 中的 DateTime.MaxValue,将时间值负无穷大转换为 .NET.MinValu。这种自动转换有时会导致一些问题,因为 .NET.MaxValue 和 .NET.MinValue 在某些情况下可能表示了一些特殊含义,例如在排序和比较时。

为了避免这个问题,可以手动调用 AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true) 方法来禁用自动转换功能。这样可以确保 PostgreSQL 中的时间值无限大和负无穷大在与 .NET 类型进行转换时不会出现意外的结果。
DateTimeOffset 和 DateTime 都是用于表示日期和时间的结构体,但它们有一些重要的区别:

时区处理方式不同:DateTime 结构体只能表示本地时间或协调世界时(UTC),而不能直接表示其他时区的时间。而 DateTimeOffset 结构体则可以直接指定一个时区,并将偏移量与日期时间一起表示。

精度不同:DateTime 对象精确到毫秒级别,DateTimeOffset 对象精确到 100 毫微秒级别。

表示范围不同:DateTime 结构体用于表示的日期范围为公历 0001 年 1 月 1 日到公历 9999 年 12 月 31 日,而 DateTimeOffset 结构体可以表示的范围更广泛,可以表示的最小值和最大值分别为 -9999 年 1 月 1 日和 10000 年 12 月 31 日。

序列化和反序列化方式不同:由于 DateTime 结构体只能表示本地时间或 UTC 时间,因此在进行跨时区传输或保存时可能会遇到问题,需要进行额外的处理。而 DateTimeOffset 结构体则可以直接序列化和反序列化,方便进行跨时区传输和保存。

总之,如果需要处理跨时区的日期和时间,更推荐使用 DateTimeOffset。而在本地时间处理中,可以使用 DateTime。

官网说明:https://www.npgsql.org/doc/types/datetime.html#timestamps-and-timezones

参考博客:https://www.cnblogs.com/VAllen/p/abp-pgsql-timestamp-with-time-zone.html

TerraMours源码地址:https://github.com/firstsaofan/TerraMours