如何阻止Entity Framework搭配PostgreSQL查询数据时转换时区
这个问题的核心在于PostgreSQL的timestamp with timezone(简称timestamptz)的存储机制,以及Npgsql驱动的默认行为:
PostgreSQL的
timestamptz并不会存储原始时区信息,它会将输入的时间转换为UTC时间存储,查询时再根据客户端连接的时区设置转换为对应时区的时间。你看到数据库中显示的2021-06-17 15:00:00.567379+07,其实是数据库客户端(比如pgAdmin)使用+7时区连接时的显示结果,实际存储的是UTC时间2021-06-17 08:00:00.567379。Npgsql驱动默认会根据你本地电脑的时区(-2)将UTC时间转换为本地时区的DateTimeOffset,这就是你得到17/06/2021 6:00:00 AM -02:00的原因。
针对你的需求(保留原始显示的+7时区),可以通过以下几种方式配置:
方法1:修改连接字符串的时区设置
直接在PostgreSQL连接字符串中指定时区为+7对应的时区ID,这样Npgsql驱动会以该时区转换UTC时间,返回你期望的+7时区的DateTimeOffset。
比如,+7时区对应的常见时区ID是Asia/Bangkok(或根据你的实际区域选择Asia/Ho_Chi_Minh),修改连接字符串如下:
Server=your_server;Port=5432;Database=your_db;User Id=your_user;Password=your_pass;TimeZone=Asia/Bangkok;
这样查询后得到的DateTimeOffset就会是17/06/2021 3:00:00 PM +07:00,和数据库显示的一致。
方法2:配置Npgsql返回UTC时间,手动转换时区
如果你希望保持驱动返回UTC时间,再自行转换为目标时区,可以在连接字符串中添加DateTimeKind=Utc参数:
Server=your_server;Port=5432;Database=your_db;User Id=your_user;Password=your_pass;DateTimeKind=Utc;
此时查询得到的DateTimeOffset会是UTC时间:17/06/2021 8:00:00 AM +00:00,之后你可以手动转换为+7时区:
var utcTime = yourEntity.YourTimeProperty; var targetTime = utcTime.ToOffset(TimeSpan.FromHours(7));
方法3:如果需要存储原始时区信息(进阶方案)
如果你真正需要保留插入时的原始时区(而不是固定转换为+7),那么timestamptz并不适合,因为它不存储时区。此时应该:
- 使用
timestamp(不带时区)类型存储时间值 - 新增一个单独的字段(比如
timezone)存储原始时区信息(可以是字符串类型,比如"+07:00"或"Asia/Bangkok") - 查询时结合这两个字段构造带时区的
DateTimeOffset
针对Entity Framework 6的额外配置
如果你使用的是EntityFramework6.Npgsql,还可以在DbContext的OnModelCreating方法中配置时间类型的映射,确保驱动正确处理DateTimeOffset:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 将实体的DateTimeOffset属性映射到PostgreSQL的timestamptz modelBuilder.Properties<DateTimeOffset>() .Configure(c => c.HasColumnType("timestamp with time zone")); }
另外,你提到SQL Server没有这个问题,是因为SQL Server的datetimeoffset类型本身会存储时间值和对应的时区偏移量,和PostgreSQL的timestamptz存储机制完全不同,所以EF的处理方式也不一样。
内容的提问来源于stack exchange,提问作者Pham Vu Tran

