Dapper 默认不支持 DateTimeOffset 自动映射,因其依赖的数据库 provider 对该类型支持有限,易导致偏移量丢失或异常;推荐统一使用 UTC DateTime 存储与处理,必要时通过 ITypeHandler 自定义实现。

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:
- 写入时:提取
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自动工作;改用() Query后手动构造()
基本上就这些。核心不是“怎么让 Dapper 支持 DateTimeOffset”,而是“要不要真需要它”——多数时候,用好 DateTime + UTC + 显式时区转换,更轻、更稳、更易维护。










