Dapper SetValue on F# Union type not being called - f#

My research on this seems to conclude a bug in dapper, but I'm hoppign I'm missing something simple.
I have a union type
type PrimaryKey<'x> =
| Id of int
| EmptyPrimaryKey
And I want to be able to map that to int columns in SQL.
My custom type handler is below:
type PrimaryKeyHandler<'X>() =
inherit SqlMapper.TypeHandler<PrimaryKey<'X>>()
override _.SetValue(param, value) =
printfn "Running set value"
let valueOrNull =
match value with
| PrimaryKey.Id id ->
box id
| EmptyPrimaryKey ->
null
param.Value <- valueOrNull
override _.Parse value =
if isNull value || value = box DBNull.Value
then EmptyPrimaryKey
else Id (value :?> int)
_.Parse is working correctly when I'm using Select queries, so all good there.
However when I'm trying to Insert a value, .SetValue does not seem to be being called at all and the program is dying with the type cannot be used as a parameter value
And when dumping out the values that's going into the query, it is indeed a PrimaryKey rather than an integer. And no printfn statements or breakpoints are being hit.
This doesn't seem to be a super uncommon problem but I haven't found a clear solution yet.

Related

Dapper column to F# option property

How do I get Dapper to convert data to an F# type with an option property? Simple example:
type SomeType = {
Count : int option
}
connection.QueryAsync<SomeType>(...)
This throws:
System.InvalidOperationException
A parameterless default constructor or one matching signature
(System.Int32 count) is required for SomeType materialization
Using Nullable instead of option works:
type SomeType = {
Count : Nullable<int>
}
But it's not as desirable for various reasons. For one thing, I have cases where I use string option (because F# doesn't allow null assignments normally, which is great), and Nullable<string> doesn't compile. Is there a way to configure/instruct Dapper to automatically deal with option types? I'd like to avoid using custom mappings every time.
In case it matters, using with Npgsql.
I don't think there's any support for adding handlers for open generics, so you'd have to add a type handler for each type you need.
You could define a handler like this:
type OptionHandler<'T>() =
inherit SqlMapper.TypeHandler<option<'T>>()
override __.SetValue(param, value) =
let valueOrNull =
match value with
| Some x -> box x
| None -> null
param.Value <- valueOrNull
override __.Parse value =
if isNull value || value = box DBNull.Value
then None
else Some (value :?> 'T)
And register for the types you need like this:
SqlMapper.AddTypeHandler (OptionHandler<string>())
SqlMapper.AddTypeHandler (OptionHandler<int>())

F# out parameters and value types

The following f# function works great if I pass references to objects, but will not accept structs, or primitives:
let TryGetFromSession (entryType:EntryType, key, [<Out>] outValue: 'T byref) =
match HttpContext.Current.Session.[entryType.ToString + key] with
| null -> outValue <- null; false
| result -> outValue <- result :?> 'T; true
If I try to call this from C# with:
bool result = false;
TryGetFromSession(TheOneCache.EntryType.SQL,key,out result)
I get The Type bool must be a reference type in order to use it as a parameter Is there a way to have the F# function handle both?
The problem is that the null value in outValue <- null restricts the type 'T to be a reference type. If it has null as a valid value, it cannot be a value type!
You can fix that by using Unchecked.defaultOf<'T> instead. This is the same as default(T) in C# and it returns either null (for reference types) or the empty/zero value for value types.
let TryGetFromSession (entryType:EntryType, key, [<Out>] outValue: 'T byref) =
match HttpContext.Current.Session.[entryType.ToString() + key] with
| null -> outValue <- Unchecked.defaultof<'T>; false
| result -> outValue <- result :?> 'T; true
I still think this is not "pretty"/idomatic F# code and would probably do some more seremonial with the following:
let myCast<'T> o =
match box o with
| :? 'T as r -> Some(r)
| _ -> None
let GetFromSession<'T> entryType key =
match HttpContext.Current.Session.[entryType.ToString + key] with
| null -> None
| r -> myCast<'T> r
This is also kind of "safer" and will (should?) not throw any exception, and it removes the null-stuff in F#. In C# it will return and work ok too, but None are returned as null, and if some result, well yeah it will be Some ;-)
Mind that the above code are not tested, not run in any setting or even compiled, so regard it as pseudo code. It might even have other issues...
Check also:
https://msdn.microsoft.com/en-us/library/dd233220.aspx
and
http://fsharpforfunandprofit.com/posts/match-expression/
On the last link especially: Matching on subtypes
On a side note, I do not like the missing checking of entire hierachy from HttpContext to Session are non-null, but that might just be me...
Update for some C# code using None/Some
var x = GetFromSession<MyTypeInSession>(entryType, key)?.Value??defaultValue;
There is absolutely no need for going full arabic, reading from right to left, and from down and up with a pyramidal scheme of ifs and buts and no candy or nuts, for null-checking et al ad nauseam.
And again code is to be regarded as pseudo code...

Why I'm getting null for a string in F#?

One of the benefits of the type system on F# is avoid a null exception... or that was something I believe... because I'm getting a null problem:
[<CLIMutable>]
type Customer = {
[<AutoIncrement>] id:option<int64>
code:string
name:string
}
I'm running a SQL code:
let SqlFTS<'T>(table:string, searchTable:string, query:string) =
use db = openDb()
let sql = sprintf "SELECT * FROM %s WHERE id in (SELECT docid FROM %s WHERE data MATCH %A)" table searchTable query
printfn "%A" sql
db.SqlList<'T>(sql) |> Seq.toArray
testCase "Customers" <|
fun _ ->
let rows = GenData.genCustomers(50)
Customers.insert(rows)
isEqual "Failed to insert" 50L (DB.SqlCount<Customers.Customer>())
//Until here, it works
let c = Customers.byId(1L)
printfn "%A" c
//Customers.byId return me a record with all the properties as NULLS!
//Then c.name is null, and the code above fail.
let rows = Customers.searchCustomers(c.name)
This was very unexpected. Why I can get a record with all values to null?
Let's start with line 1:
[<CLIMutable>]
The docs for CLIMutable state
Adding this attribute to a record type causes it to be compiled to a Common Language Infrastructure (CLI) representation with a default constructor with property getters and setters.
That default constructor means the fields will be initialized to default values. The default value for string is null. That's valid to the CLR and it's valid to C# and VB.NET. You may not be able to call the default ctor from F#, but pretty much anyone else can.
Welcome to interop; it can be a pain.

How can I cast and check for NULL all at the same time in F#?

In C# one can use as for casting reference type values to either a requested type or null, so that it's only needed for the cast value to be checked for being null before being used. How do I do it in F#?
You can use pattern matching and the :? <type> as <value> pattern. F# does not like null values so it does not automatically give you null if the value is not of the right type (or if it was null previously). You can handle null and values of other types in a second branch:
let o = box (System.Random())
match o with
| :? System.Random as rnd -> rnd.Next()
| _ -> -1
If you really wanted to get null value, you could use Unchecked.defaultof, but that is probably not a good idea and it could lead to errors:
let castAs<'T> (o:obj) =
match o with :? 'T as t -> t | _ -> Unchecked.defaultof<'T>
castAs<System.Random> null // = null
castAs<System.Random> "hi" // = null
castAs<System.Random> (box (System.Random())) // = random

How do I cast a "string" into a "option<string>"

I have a function declared as
let GetLength (value : option<string>) =
if value.IsSome then value.Value.Length else 0
And I have the variable
let a : string = "tom"
How do I pass a to the function GetLength?
The accepted answer doesn't compile, and produces...
GetLength Some a;;
^^^^^^^^^^^^^^
error FS0003: This value is not a function and cannot be applied
F# thinks you are building a function (GetLength Some) to apply to the value a. That is because it's a functional language.
The correct form is
GetLength (Some a);;
You don't cast it. You need to use the Some constructor:
GetLength Some a
An alternative to parentheses:
GetLength <| Some a
I think it's important to address this question:
Why would anyone willing type 18
tokens over 3 lines when you can get
the exact same thing using 12 tokens
on one line?
Writing code isn't just about conciseness - it's also about readability and maintainability. Suppose you need to handle the case where a is null. With pattern matching, you could go from this:
let GetLength (value : string option) =
match value with
| Some s -> s.Length
| _ -> 0
To this:
let GetLength (value : string option) =
match value with
| Some s when s <> null -> s.Length
| _ -> 0
To an F# programmer, the meaning is clear. To fix your implementation would look something like this:
let GetLength (value : option<string>) =
if value.IsSome && value.Value <> null then value.Value.Length else 0
The result might be the same, but I don't find it particularly easy to see, at a glance, what's happening.
It's fine if pattern matching doesn't resonate with you, but the extra "cost" in the simple case is often made up for many times over as the logic evolves.
To answer the general question of casting 'a to option<'a>, if you wanted to do it safely without just applying the object to the Some constructor, there is a simple function that I would use:
let to_option = function
| null -> None
| obj -> Some obj

Resources