I've registered a custom type handler for Dapper to Json serialize a string. This works as expected. However, whilst coding, I discovered two pieces of code after refactoring, the latter one which didn't trigger the datatype handler, but should in my opinion, so what's the difference?
First the code that works as expected - the custom type handler is called by dapper, and the data is serialized when inserted into the table:
if (val.GetType() != typeof (String))
{
var v = new JsonString {Value = val};
this.Connection.Execute("insert into misc (k,v) values (@keyName, @v)",
new { keyName, v });
}
else
{
this.Connection.Execute("insert into misc (k,v) values (@keyName, @val)",
new { keyName, val });
}
Now for the code which doesn't work as it inserts into the table the fully qualified type string instead of the serialized data, but which I think is semantically similar:
var v = val.GetType() != typeof (String)
? new JsonString {Value = val}
: val;
// t is set to type of JsonString, therefore the dapper type handler should be used
var t = v.GetType();
this.Connection.Execute("insert into misc (k,v) values (@keyName, @v)",
new { keyName, v });
Is it a dapper bug or a c# oddity? I assume it's something to do with the auto typing in the ternary conditional which is the failing point, but t
is set to the data type that is serialized by Dapper (or rather the custom type handler). What's the difference?
I am going to assume that the compile-time type (i.e. declaration type) of val
is System.Object
. I will explain why the two situations are not equivalent.
One must be careful to distinguish between the compile-time type of a variable and the actual run-time type (which is found by .GetType()
).
In the first piece of code, in the branch where val
is not String
at run-time, we declare:
var v = new JsonString {Value = val};
Here var
is substituted by JsonString
since that is the compile-time type of the right-hand side of the =
assignment. Then the anonymous type instance:
new { keyName, v }
is going to be a class (I will call it class Anonymous1
) with a member
public JsonString v { get { ... } }
Now, in the second piece of code, we have instead:
var v = val.GetType() != typeof (String)
? new JsonString {Value = val}
: val;
The compile-time types of the operands to the ternary operator are:
{bool} ? {JsonString} : {object}
At compile-time, a best common type for JsonString
and object
must be found. That common type is object
. So object
becomes the compile-time type of v
here, i.e. var
means object
here.
Then the anonymous type:
new { keyName, v }
is a type "Anonumous2
" whose "2nd" property reads:
public object v { get { ... } }
So to sum up: In the first case you pass in an object which has a property v
declared as a JsonString
which when retrieved returns a JsonSting
that happens to have run-time JsonString
. In the second code sample you pass in an object which has a property v
declared as object
which when retrieved returns an object
that happens to have run-time type JsonString
.
I do not know much on how Dapper works! But presumably when it sees (by reflection?) that the property type is object
, it simply calls .ToString()
on it. If that object
happens to be of run-time type string
, that should be OK, since string.ToString()
is overridden. But JsonString.ToString()
is not like that.
When Dapper sees the property is declared as JsonString
, Dapper does something smarter than calling .ToString()
.
Assuming there is no implicit conversion available between JsonString and String, the code in the second example won't work. If there is an implicit conversion available, you need to provide more information about the exception that is occurring.
WIth no conversion available between JsonString, a variable declared with type var has a type that is inferred by the compiler ((https://msdn.microsoft.com/en-us/library/bb384061.aspx). In this case, variable v is either of type JsonString or String depending upon which part of the assignment occurs. If the compiler assumes it is of type JsonString, any assignment with a String will fail.
In your second code example, the first line of code is wrong since the assignment results in two different data types getting assign to variable v.