I'm using union types similar to enums on my dapper objects:
type Confidence =
| Low
| Medium
| High
type Goal = {
Confidence: Confidence
...
}
I've created a custom type handler in order to make it work:
type UnionHandler<'T>() =
inherit SqlMapper.TypeHandler<'T>()
override __.SetValue(param, value) =
param.Value <- value.ToString()
()
override x.Parse(value: obj) =
Union.parse <| string value
let registerTypeHandlers() =
SqlMapper.AddTypeHandler (UnionHandler<Confidence>())
This works fine, but it would be even nicer if I didn't have to register a new one for each new union type.
Is it possible to make the type handler generic in such a way that it can handle all union types with only one registration?
This can be done with Reflection:
let internal addUnionTypeHandlers() =
let assembly = Assembly.GetExecutingAssembly()
let unionHandlerType =
assembly.GetTypes()
|> Seq.filter(fun t -> t.Name.Contains("UnionHandler") && t.IsGenericTypeDefinition)
|> Seq.head
assembly.GetTypes()
|> Seq.filter(fun t -> not t.IsGenericType && FSharpType.IsUnion(t, BindingFlags.Default))
|> Seq.iter(fun t ->
let ctor = unionHandlerType
.MakeGenericType(t)
.GetConstructor(Array.empty)
.Invoke(Array.empty)
(typeof<SqlMapper>.GetMethods()
|> Seq.filter(fun methodInfo ->
if methodInfo.Name = "AddTypeHandler" && methodInfo.IsGenericMethodDefinition then
let gp = methodInfo.GetParameters()
not <| isNull gp && gp.Length = 1 && gp.[0].ParameterType.Name.Contains("TypeHandler")
else false)
|> Seq.head)
.MakeGenericMethod(t)
.Invoke(null, [| ctor |]) |> ignore
)
Note:
It would have been much simpler if Dapper have had the signature of AddTypeHandler in a form ITypeHandler -> unit. But it accepts TypeHandler and in addition has overloaded version. So we need GMD for method AddTypeHandler and instantiate it with method MakeGenericMethod and then call this method with parameter which we obtains from GetConstructor ... Invoke
Playing further with reflection you can decide to mark some discriminated unions with some attribute to ignore adding the mapping. You can extend code to analyse if type has attribute. Also you can do manipulations on module basis I assume using FSharpType.IsModule