Npgsql COPY 写入 0001-01-01 后变成 -infinity
Npgsql 10.0.3 二进制导入最小日期后,按有限日期查询却查不到。先区分 DateTime.MinValue 的无穷值映射与时区类型,再决定是否关闭该映射。
用 NpgsqlBinaryImporter 向 timestamp 列写入 0001-01-01 00:00:00,导入成功,下面的查询却找不到这行:
SELECT datetime_column
FROM test
WHERE datetime_column = '0001-01-01 00:00:00'::timestamp;
同一行能被 datetime_column = '-infinity'::timestamp 查到。这种情况下先检查无穷值映射。Npgsql 10.0.3 中,new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Unspecified) 正好是 DateTime.MinValue;它不是任意一个普通的历史日期。
最小值有特殊含义
从 Npgsql 6.0 起,默认规则把 PostgreSQL 的 -infinity、infinity 与对应 .NET 日期类型的最小值、最大值互相映射。二进制 COPY 同样经过日期转换器。将参数声明为 NpgsqlDbType.Timestamp,只明确了 PostgreSQL 类型,不会取消这个规则。
0001-01-01 00:00:01 不等于最小值,因此可能正常保存为有限日期。这个差别可以帮助定位,但不要给真实数据统一加一秒:那会改变业务含义。
也要核对数据库里的实际值。若只在 C# 中读回并打印 DateTime.MinValue,有限最小日期和 -infinity 的默认映射可能看起来一样。用只读 SQL 对比有限值和无穷值,才能区分存储内容。
确实需要有限最小日期时
如果业务要求把公元1年1月1日保存为有限日期,应在应用启动时、任何 Npgsql 操作之前设置:
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
随后再创建数据源、连接和导入器。不要等连接池已经投入使用后才切换,也不要把它放在某个请求的 COPY 调用旁边。这是进程级兼容开关,影响该进程的日期无穷值转换,不能当成只针对某一列的设置。
上线前先查明应用是否还依赖 DateTime.MaxValue、DateOnly 或 NodaTime 的极值来表达无穷范围。关闭转换后,已经存储的 PostgreSQL 无穷值也不能再按原先的极值映射理解。对仍需无穷语义的业务,要先确定类型和数据处理约定,再改变全局规则。
旧连接串参数 Convert Infinity DateTime 已移除,不应把旧版本配置照搬到10.0.3。Npgsql.EnableLegacyTimestampBehavior 控制的是另一套时间戳兼容行为,也不是这里的替代开关。
分开检查有限日期与时间戳类型
在隔离的业务测试库中,覆盖有限最小日期、普通日期以及应用实际使用的无穷值。导入后用 SQL 确认保存的是有限值,再验证读取和再次写回,不要只检查 COPY 没抛异常。
若错误变成“只支持 UTC”,需要另查参数最终被绑定为 timestamp with time zone 还是 timestamp without time zone,可对照Npgsql 时间戳参数类型检查。无穷值映射与时间戳类型是两项独立规则,修复其中一项不代表另一项也已正确。


