如何编写适用于F#可区分联合类型的通用Dapper TypeHandler?
通用F#可区分联合Dapper TypeHandler实现方案
要解决通用Dapper TypeHandler中Parse方法的静态转换限制,核心是利用F#反射API处理可区分联合(DU)的实例构造与值提取,而非直接强制类型转换。以下是完整的实现方案:
1. 可区分联合示例定义
先给出两种常见的DU类型(单案例、多案例)作为测试基准:
open System // 单案例带参数的可区分联合 type UserId = UserId of Guid type EmailAddress = EmailAddress of string // 多案例无参数的可区分联合 type OrderStatus = | Pending | Completed | Cancelled
2. 通用TypeHandler实现
借助Microsoft.FSharp.Reflection命名空间下的API,实现同时支持单案例和多案例DU的通用处理程序:
open Dapper open System open Microsoft.FSharp.Reflection type GenericDuTypeHandler<'T>() = inherit TypeHandler<'T>() override _.SetValue(param, value) = let unionCases = FSharpType.GetUnionCases(typeof<'T>) match unionCases with | [| singleCase |] -> // 单案例DU:提取内部参数值存入数据库 let innerValues = FSharpValue.GetUnionFields(value, typeof<'T>) |> snd param.Value <- innerValues.[0] | _ -> // 多案例DU:存入案例的名称(可根据需求改为Tag值) let case = FSharpValue.GetUnionFields(value, typeof<'T>) |> fst param.Value <- case.Name override _.Parse(value: obj) = if value = null || value = DBNull.Value then failwith "Cannot parse null value to DU type" let unionCases = FSharpType.GetUnionCases(typeof<'T>) match unionCases with | [| singleCase |] -> // 单案例DU:用数据库值构造DU实例 let fieldType = singleCase.GetFields().[0].PropertyType let convertedValue = Convert.ChangeType(value, fieldType) FSharpValue.MakeUnion(singleCase, [| convertedValue |]) :?> 'T | _ -> // 多案例DU:根据数据库存储的案例名称匹配构造 let caseName = value.ToString() let targetCase = unionCases |> Array.find (fun c -> c.Name.Equals(caseName, StringComparison.OrdinalIgnoreCase)) FSharpValue.MakeUnion(targetCase, [||]) :?> 'T
3. 关键逻辑说明
- 单案例DU处理:通过
FSharpValue.GetUnionFields提取内部参数,用FSharpValue.MakeUnion反向构造实例,解决了静态强制转换无法创建DU实例的问题。 - 多案例DU处理:默认将案例名称字符串存入数据库,读取时通过名称匹配案例构造实例;如果需要用数字Tag存储,只需将
case.Name替换为case.Tag.ToString(),Parse时匹配Tag值即可。 - 空值处理:添加了基础的空值判断,可根据业务需求调整为返回默认值或忽略。
4. 注册与使用
在Dapper初始化时注册对应的类型处理程序即可:
// 注册各DU类型的通用Handler SqlMapper.AddTypeHandler(GenericDuTypeHandler<UserId>()) SqlMapper.AddTypeHandler(GenericDuTypeHandler<EmailAddress>()) SqlMapper.AddTypeHandler(GenericDuTypeHandler<OrderStatus>()) // 之后即可正常在Dapper查询/命令中使用DU类型 let getUserId (id: Guid) = connection.QuerySingleOrDefault<UserId>("SELECT Id FROM Users WHERE Id = @Id", {| Id = id |})
内容的提问来源于stack exchange,提问作者Priyanka Jain
相关产品推荐
相关产品推荐

