Dapper同步PostgreSQL到SQL Server2012时日期自动转UTC如何解决
这个自动时区转换和Dapper无关,是两层数据库驱动的默认行为共同导致的:
- Npgsql(.NET 访问PostgreSQL的官方驱动)读取
TIMESTAMP WITH TIME ZONE类型字段时,默认会将值自动转换为UTC时间,同时给返回的DateTime对象标记DateTimeKind.Utc - 写入SQL Server时,SqlClient驱动检测到传入的
DateTimeKind为Utc,会自动根据当前系统时区做偏移校正,最终存入DATETIME2字段的就是转换后的UTC值,和PostgreSQL端存储的BST本地时间不一致。
按改造成本从低到高排序,选一个即可:
方案1:配置PostgreSQL连接串,全局关闭自动时区转换
这是成本最低的方案,不需要修改任何业务查询或实体代码,直接在PostgreSQL的连接字符串中添加参数即可:
Host=你的PG实例地址;Port=5432;Database=业务库名;Username=只读账号;Password=账号密码;TimestampTZKind=Unspecified
核心参数是TimestampTZKind=Unspecified,配置后Npgsql读取TIMESTAMP WITH TIME ZONE字段时,会直接返回数据库中存储的原始时间值,不做任何时区转换,返回的DateTime对象Kind标记为Unspecified。后续SqlClient写入SQL Server时,遇到Kind为Unspecified的DateTime不会做任何时区校正,会将值原样存入DATETIME2字段,完全满足两端值一致的要求。
如果项目使用的Npgsql版本低于6.0(.NET Core 3.1默认常搭配5.x版本Npgsql),不支持上述连接串参数,可以在程序启动时(比如Windows服务的Program.cs初始化阶段)添加全局开关:
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
开启旧版时间戳行为后,Npgsql读取timestamptz类型时不会自动转换为UTC,返回值Kind为Unspecified,效果和上述连接串配置一致。
方案2:查询阶段在PG侧转换为无时区时间
如果不方便调整Npgsql的全局配置,由于你持有PG库的只读权限,可以直接修改查询SQL,用PG内置的AT TIME ZONE语法将带时区的时间值转换为BST本地时间的无时区格式:
SELECT -- 其余业务字段保持不变 目标时间字段 AT TIME ZONE 'BST' AS 目标时间字段 FROM 业务表
这种方式查出来的结果本身就是不带时区信息的BST本地时间,Npgsql读取时不会做任何转换,写入SQL Server时也不会产生偏移。
方案3:手动修正DateTime的Kind标记
如果时间字段数量很少,也可以在从PG查询出数据后、写入SQL Server前,批量将所有时间字段的Kind手动修改为Unspecified,阻断SqlClient的自动转换逻辑:
// 从PG查询数据 var dataList = pgConnection.Query<BusinessEntity>(querySql).ToList(); // 遍历修正所有时间字段的Kind foreach (var item in dataList) { item.CreateTime = DateTime.SpecifyKind(item.CreateTime, DateTimeKind.Unspecified); item.UpdateTime = DateTime.SpecifyKind(item.UpdateTime, DateTimeKind.Unspecified); // 其余时间字段按相同逻辑处理 } // 写入SQL Server sqlConnection.Execute(insertSql, dataList);
该方案的缺点是每新增一个时间字段都需要手动加对应处理代码,容易遗漏,仅适合时间字段极少的场景。
调整完成后可以做简单验证:从PG查出一条带已知时间值的记录,打印对应DateTime的数值和Kind属性,如果数值和PG库中直接查询看到的完全一致,且Kind为Unspecified,写入SQL Server后的值就会和PG端完全对齐,不会出现时区转换偏差。
内容的提问来源于stack exchange,提问作者TCBW

