Dzoukr / Dapper.FSharp

Lightweight F# extension for StackOverflow Dapper with support for MSSQL, MySQL, PostgreSQL, and SQLite
MIT License
365 stars 35 forks source link

How do typehandlers in F# work? #88

Open dredgy opened 1 year ago

dredgy commented 1 year ago

Sorry, this is not an issue with Dapper.Fsharp but I'm creating a new library that has some overlap and would be appreciative of any help you could offer. I'm not very familiar with Dapper and am considering using a custom approach but going to try to stick with Dapper for now.

I have created a custom type handler for a generic union type and whilst parseValue is being called and working correctly on select, setValue is never being called.

Is there anything special I have to do? I have defined it as such.

type PrimaryKey<'x> =
    | Id of int
    | EmptyPrimaryKey

[<CLIMutable>]
type Address = {
    id: PrimaryKey<Address>
    address: string
} with
    static member DatabaseTable = "addresses"

type PrimaryKeyHandler<'X>() =
    inherit SqlMapper.TypeHandler<PrimaryKey<'X>>()

    override _.SetValue(param, value) =

        let valueOrNull =
            match value with
            | PrimaryKey.Id x -> box x
            | EmptyPrimaryKey -> DBNull.Value

        param.Value <- valueOrNull

    override _.Parse value =
        if isNull value || value = box DBNull.Value
        then EmptyPrimaryKey
        else PrimaryKey.Id (value :?> int)

And registering it as such

   let PKeyHandler = typedefof<PrimaryKeyHandler<_>>

    assembly.GetTypes()
        |> Seq.filter(fun t ->
            FSharpType.IsRecord(t) && t.GetProperty("DatabaseTable") <> null
        )
        |> Seq.iter(fun t ->
            printfn $"TypeHandler for: PrimaryKey<{t.Name}> Registered"
            let ctor = PKeyHandler
                            .MakeGenericType(t)
                            .GetConstructor(Array.empty)
                            .Invoke(Array.empty)

            let pkt = typedefof<PrimaryKey<_>>.MakeGenericType(t)
            SqlMapper.AddTypeHandler(pkt, (ctor :?> SqlMapper.ITypeHandler))
        )

Anything jump out? I'm hoping I've just made a simple blunder. Do I have to register each union case a separate typehandler? That seems gross, and the optionHandler works fine so wouldn't think so