Dapper 默认不支持
DateTimeOffset类型的自动映射,这是它和 EF 等全功能 ORM 的关键区别之一。直接查询或参数传入
DateTimeOffset会报错或返回 null,必须通过自定义方式处理。
为什么 Dapper 不原生支持 DateTimeOffset
Dapper 的底层读取依赖数据库 provider 的
IDataReader索引器(如
SqliteDataReader或
SqlDataReader),而多数 provider 对
DateTimeOffset的支持有限——尤其当数据库字段是
DATETIME而非
DATETIMEOFFSET时,值可能被截断、丢失偏移量,甚至抛出
InvalidCastException。
官方明确说明:Dapper 不能处理
DateTimeOffset、
Guid和
TimeSpan这三类类型,除非你提供
ITypeHandler。
推荐做法:统一用 UTC + DateTime 处理
绝大多数生产系统不需要存储偏移量本身,只需保证时间一致性。最佳实践是:
数据库字段使用DATETIME2(SQL Server)或
TIMESTAMP WITHOUT TIME ZONE(PostgreSQL),只存 UTC 时间 C# 层统一用
DateTime.UtcNow写入,用
.ToUniversalTime()或
.Kind == DateTimeKind.Utc校验 读取后按需转换为本地时间或指定时区:
TimeZoneInfo.ConvertTimeFromUtc(dt, tz)避免在业务逻辑中混用
DateTime.Now,防止部署多时区服务器时出现时间漂移
必须用 DateTimeOffset?那就写 TypeHandler
如果你的场景强依赖偏移量(比如日志精确归属、跨时区调度),可以实现一个
ITypeHandler<datetimeoffset></datetimeoffset>: 写入时:提取
value.DateTime存为
DATETIME,同时把
value.Offset单独存一列(如
offset_minutes INT) 读取时:从两列拼回
new DateTimeOffset(dateTime, TimeSpan.FromMinutes(offset))或者数据库用
DATETIMEOFFSET字段,handler 中调用
parameter.Value = value;(SQL Server provider 支持该类型直传)
注册方式:
SqlMapper.AddTypeHandler(new DateTimeOffsetHandler());,建议在应用启动时全局注册一次。
动态参数和查询中的常见坑
用
DynamicParameters传
DateTimeOffset时,Dapper 会尝试隐式转成
DateTime,但丢掉偏移量: 错误写法:
args.Add("@when", dto); → 可能变成本地时间或 UTC,不可控
安全写法:args.Add("@when", dto.UtcDateTime); args.Add("@offset", dto.Offset.TotalMinutes);
查询返回时,不要指望 Query<datetimeoffset>()</datetimeoffset>自动工作;改用
Query<dynamic>()</dynamic>后手动构造
基本上就这些。核心不是“怎么让 Dapper 支持 DateTimeOffset”,而是“要不要真需要它”——多数时候,用好
DateTime+ UTC + 显式时区转换,更轻、更稳、更易维护。
