在使用Dapper进行数据库操作时,SQL Server中的time类型字段无法直接映射到C#的TimeSpan类型,这是因为Dapper默认的映射规则中没有包含这两种类型的转换逻辑,需要开发者手动实现自定义映射来处理这类场景。

问题背景
SQL Server的time类型用于存储时间值,精度可以精确到100纳秒,而C#中的TimeSpan类型表示时间间隔,二者的存储逻辑和范围存在差异。当实体类中定义TimeSpan类型的属性,对应数据库表的time类型字段时,直接执行查询操作会抛出类型转换异常,因为Dapper无法自动识别并完成二者的转换。
自定义类型映射实现
Dapper提供了自定义类型映射的扩展能力,我们可以通过实现SqlMapper.TypeHandler<T>接口来定义TimeSpan和time类型的转换规则。
自定义TypeHandler实现
首先创建TimeSpan类型的处理器,实现从数据库time值到C# TimeSpan的转换,以及从TimeSpan到数据库time值的转换:
using Dapper;
using System;
using System.Data;
public class TimeSpanTypeHandler : SqlMapper.TypeHandler<TimeSpan>
{
// 从数据库读取值转换为TimeSpan
public override TimeSpan Parse(object value)
{
// SQL Server的time类型读取出来是TimeSpan类型,直接转换即可
if (value is TimeSpan timeSpanValue)
{
return timeSpanValue;
}
// 处理其他可能的类型,比如字符串形式的time值
if (value is string stringValue && TimeSpan.TryParse(stringValue, out var result))
{
return result;
}
throw new ArgumentException($"无法将{value?.GetType()}类型转换为TimeSpan,值为:{value}");
}
// 将TimeSpan转换为数据库可存储的值
public override void SetValue(IDbDataParameter parameter, TimeSpan value)
{
// 设置参数类型为time
parameter.Value = value;
parameter.DbType = DbType.Time;
}
}
注册自定义映射处理器
定义好处理器之后,需要在程序启动时注册这个处理器,让Dapper在映射时可以使用该规则:
using Dapper;
using System;
class Program
{
static void Main(string[] args)
{
// 注册TimeSpan类型的自定义处理器
SqlMapper.AddTypeHandler(new TimeSpanTypeHandler());
// 后续执行Dapper操作时会自动使用这个映射规则
// 下面是测试代码示例
TestDapperTimeMapping();
}
static void TestDapperTimeMapping()
{
// 假设已经创建了数据库连接
using (var connection = new System.Data.SqlClient.SqlConnection("Server=.;Database=TestDB;Trusted_Connection=True;"))
{
// 创建测试表,包含time类型字段
connection.Execute("IF OBJECT_ID('TestTimeTable', 'U') IS NOT NULL DROP TABLE TestTimeTable; CREATE TABLE TestTimeTable (Id INT IDENTITY(1,1) PRIMARY KEY, WorkTime TIME)");
// 插入测试数据,TimeSpan类型会自动转换为time类型
var insertSql = "INSERT INTO TestTimeTable (WorkTime) VALUES (@WorkTime)";
connection.Execute(insertSql, new { WorkTime = new TimeSpan(8, 30, 0) }); // 8小时30分钟
// 查询数据,time类型会自动转换为TimeSpan
var querySql = "SELECT WorkTime FROM TestTimeTable";
var result = connection.QueryFirst<TimeSpan>(querySql);
Console.WriteLine($"查询到的WorkTime值为:{result}"); // 输出 08:30:00
}
}
}
注意事项
- 注册自定义处理器的操作只需要执行一次,建议在程序入口处完成,避免重复注册。
- SQL Server的time类型精度最高支持到100纳秒,而TimeSpan的精度是1纳秒,大部分场景下二者可以正常匹配,若time类型设置了较低的精度,转换时不会丢失数据。
- 如果实体类中的TimeSpan属性允许为null,需要额外实现
SqlMapper.TypeHandler<TimeSpan?>来处理可空类型的映射,实现逻辑和上述非空类型类似,只需要增加null值的判断即可。 - 除了SQL Server的time类型,其他数据库的类似时间类型也可以参考这个思路实现自定义映射,只需要调整Parse和SetValue方法中的转换逻辑即可。
可空TimeSpan的映射处理
如果实体类中的属性是TimeSpan?可空类型,对应的数据库time字段也允许为null,可以按照以下方式实现处理器:
using Dapper;
using System;
using System.Data;
public class NullableTimeSpanTypeHandler : SqlMapper.TypeHandler<TimeSpan?>
{
public override TimeSpan? Parse(object value)
{
if (value == null || value == DBNull.Value)
{
return null;
}
if (value is TimeSpan timeSpanValue)
{
return timeSpanValue;
}
if (value is string stringValue && TimeSpan.TryParse(stringValue, out var result))
{
return result;
}
throw new ArgumentException($"无法将{value?.GetType()}类型转换为TimeSpan?,值为:{value}");
}
public override void SetValue(IDbDataParameter parameter, TimeSpan? value)
{
if (value.HasValue)
{
parameter.Value = value.Value;
parameter.DbType = DbType.Time;
}
else
{
parameter.Value = DBNull.Value;
}
}
}
注册时同样调用SqlMapper.AddTypeHandler(new NullableTimeSpanTypeHandler())即可完成可空类型的映射配置。