.NET 7 TimeOnly与Postgres无时区时间类型兼容问题排查
问题根源
PostgreSQL的time类型允许24:00:00作为合法值,用来表示一天的结束(等价于次日的00:00:00),但.NET的TimeOnly类型最大值为23:59:59.9999999。当EF Core/Npgsql尝试将数据库中的24:00:00映射到TimeOnly属性时,就会触发ArgumentOutOfRangeException。
你遇到的毫秒相关现象是因为:当时间为24:00:00.000时,PostgreSQL会省略毫秒显示为24:00:00,此时映射直接触发异常;而如果是24:00:00.001这类带非零毫秒的值,Npgsql会自动将其转换为次日的00:00:00.001,刚好符合TimeOnly的范围,所以不会报错——但这只是巧合,并非根本解决方式。
处理方案
方案1:数据库层面修正数据
直接将数据库中24:00:00的记录替换为00:00:00,两者语义等价(均表示一天结束/次日开始),可彻底避免映射冲突:
UPDATE Shift SET "End" = '00:00:00' WHERE "End" = '24:00:00';
同时修改数据写入逻辑,禁止插入/更新24:00:00,统一用00:00:00替代。
方案2:EF Core自定义映射转换
通过EF Core的值转换器,在映射时自动处理24:00:00的转换:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Shift>() .Property(s => s.End) .HasConversion( // 从TimeOnly转数据库time:将MinValue转为24小时跨度 time => time == TimeOnly.MinValue ? TimeSpan.FromHours(24) : time.ToTimeSpan(), // 从数据库time转TimeOnly:将24小时跨度转为MinValue span => span.TotalHours == 24 ? TimeOnly.MinValue : TimeOnly.FromTimeSpan(span) ); }
此方案无需修改数据库数据,由ORM层自动兼容两种时间表示。
方案3:改用TimeSpan类型映射
如果业务逻辑允许,可将模型中的TimeOnly替换为TimeSpan:
public partial class Shift { public TimeSpan Start { get; set; } public TimeSpan End { get; set; } }
PostgreSQL的time类型本质是时间跨度,24:00:00对应TimeSpan.FromHours(24),在.NET中是合法值。后续业务中如需TimeOnly,可手动转换(注意将TimeSpan.FromHours(24)转为TimeOnly.MinValue)。
内容的提问来源于stack exchange,提问作者Jamie

