Npgsql EF Core未将DateTimeOffset以UTC时间持久化到PostgreSQL
在控制台应用中使用Npgsql.EntityFrameworkCore.PostgreSQL 7.0.4,将DateTimeOffset持久化到PostgreSQL数据库时,存储的值始终显示为本地DateTimeOffset。本地机器时区为IST(UTC+05:30)。
代码示例
DbContext实现
public class TestContext : DbContext { public DbSet<Category> Categories { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseNpgsql("Host=localhost;Database=EFDateTime;User id=postgres;Password=xxxx;") .EnableSensitiveDataLogging() .LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information); } }
Category实体
public class Category { public int Id { get; set; } public DateTimeOffset DateTimeProperty { get; set; } = DateTimeOffset.UtcNow; public DateTimeOffset DateTimeOffsetProperty { get; set; } = DateTimeOffset.UtcNow; }
保存数据代码
var context = new TestContext(); var category = new Category(); context.Categories.Add(category); context.SaveChanges();
异常现象
EF Core生成的INSERT语句参数为UTC时间(2023-08-07T11:02:16.4694593+00:00),但数据库查询显示的却是带+05:30偏移的本地时间,具体对比如下:
| 值类型 | DateTimeProperty | DateTimeOffsetProperty | Kind |
|---|---|---|---|
| 查询生成值 | 2023-08-07T11:02:16.4694577+00:00 | 2023-08-07T11:02:16.4694593+00:00 | UTC |
| 数据库存储显示值 | 2023-08-07 16:32:16.469457+05:30 | 2023-08-07 16:32:16.469459+05:30 | Local |
迁移脚本显示字段最终类型为timestamp with time zone:
CREATE TABLE "Categories" ( "Id" integer GENERATED BY DEFAULT AS IDENTITY, "DateTimeProperty" timestamp without time zone NOT NULL, "DateTimeOffsetProperty" timestamp with time zone NOT NULL, CONSTRAINT "PK_Categories" PRIMARY KEY ("Id") ); -- 后续迁移将DateTimeProperty改为带时区类型 ALTER TABLE "Categories" ALTER COLUMN "DateTimeProperty" TYPE timestamp with time zone;
PostgreSQL的timestamp with time zone(简称timestamptz)类型不会存储时区偏移信息,它会把输入的时间转换为UTC后存储;但查询时,PostgreSQL会根据当前数据库会话的时区设置,将UTC时间转换为会话时区对应的本地时间返回。
你的数据库会话默认使用本地机器的IST时区(UTC+05:30),所以查询时自动将底层存储的UTC时间转换为IST时间显示,造成“存储了本地时间”的错觉,实际上数据库底层存储的是正确的UTC时间。
另外,EF Core日志中参数的DbType为DateTime,是因为Npgsql处理DateTimeOffset时,默认会将其转换为UTC的DateTime(Kind=UTC)发送给PostgreSQL。
1. 验证数据库底层存储的是UTC时间
执行以下SQL查询,直接查看UTC形式的存储值:
SELECT "DateTimeOffsetProperty" AT TIME ZONE 'UTC' FROM "Categories";
返回结果应与EF Core日志中的UTC时间一致,证明底层存储正确。
2. 修改数据库会话时区为UTC(推荐)
方法一:连接字符串指定时区
修改UseNpgsql的连接字符串,添加TimeZone=UTC参数:
optionsBuilder.UseNpgsql("Host=localhost;Database=EFDateTime;User id=postgres;Password=xxxx;TimeZone=UTC;")
通过该连接的所有会话都会使用UTC时区,查询直接返回UTC时间。
方法二:修改数据库默认时区
执行SQL命令修改数据库默认时区:
ALTER DATABASE "EFDateTime" SET TIME ZONE 'UTC';
修改后需重启数据库连接生效。
3. 显式配置EF Core属性映射
在DbContext的OnModelCreating中,显式指定属性映射到timestamptz类型,确保类型转换正确:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Category>() .Property(c => c.DateTimeProperty) .HasColumnType("timestamptz"); modelBuilder.Entity<Category>() .Property(c => c.DateTimeOffsetProperty) .HasColumnType("timestamptz"); }
内容的提问来源于stack exchange,提问作者fingers10

