You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写适用于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 01:25:21