Whenever you do something “custom” there is always a trade-off.
Sometimes is a cost of maintaining your customization and sometimes is just a cost of “swimming upstream”, doing that whatever you are doing in a different way than most people.
NMoneys customization
In NMoneys there are quite a few custom things. Amongst the ones I regret the most (if not top one) is serialization.
Serialization is the process of saving an instance to a persistent storage with the hope of bringing that serialized instance to memory in the very same shape it was saved in the first place.
Having been hit with serialization issues of monetary quantities in the past (in the previous incarnation of NMoneys, before becoming Open Source) I set to solve those problems in the library itself. And that is why instances of Money and Currency have baked-in serialization concerns.
The .NET framework handles several flavors of serialization: binary serialization, ye olde XML serialization, the relatively newer Data Contract serialization, XAML serialization,… In NMoneys, binary and XML and Data Contract serialization are customized. For XAML, I did not succeed after a couple of tries.
And at the very least, one of the choices in the customization has proved to be tricky: writing members in camelCase.
Persisting to RavenDB
My company and I have been using RavenDB a lot lately. I am a big fan of document databases and RavenDB has an edge over other products by having a pretty good story with free-text search, due to its usage of Lucene.Net.
RavenDB stores JSON documents and uses a version of Json.NET to serialize and deserialize .NET objects.
Our case (all the pun is intended)
We have the prototypical example of an Order
that may contain multiple Line
s with lines that we are storing in the database:
There is also an Snapshot
that contains a subset of the information, plus some information that can be projected from the object hierarchy itself:
Project as it was 1999
If documents representing Order
are stored in RavenDB and we wanted to get Snapshot
results, a possible solution would be creating an index to query only those orders that belong to a given owner and project the resulting Order
s into
Candid and Naïve
Money includes a code that performs a summation of a list of instances. That is exactly what we want to achieve in Snapshot.Total.
Let’s put our Ralph Wiggum’s brain on and create a result transformer that aggregates the price of each line using Money.Total()
:
What would be the result of this code that uses our naïve transformer?
Would it print the correct sum of all the prices of the documents? If it would, why is there two more sections in the post?
If does not. It throws an exception when creating the transformer. RavenDB cannot compile the expression of the transformer. Remember that result transformers are “compiled into code” that is executed in the server prior to the return of the results of a query. And, unfortunately, RavenDB does not know about NMoneys, so it cannot interpret Money.Total()
.
Default Custom Serialization… uh?
When left to its own devices, that is, not customizing anything of the way RavenDB saves the object to the database, the price of a line is stored with lowercase properties, due to the fact that ISerializable.GetObjectData()
is called whenever a Money
instance gets serialized and that method writes lowercase property names.
With that knowledge, let’s write a transformer that projects into Snapshot
and uses regular summation for amounts:
And the result when querying using the new transformer?
To my surprise, it does not crash but it does not work either. LineCount
contains the right sum of line items but Total
is a surprising (at least for me it was) zero.
Why? It took me a while to figure out, but it makes sense once you find out: we mentioned that the transformation happens in the server. But serialization happens in the client, so when we tell to sum all amounts for a line price, it is trying to sum a property named Amount
inside the Price
, since it is translating the expression without knowing the custom rules of money serialization and so it is summing the default for a decimal
, which is zero.
De-customize
If the problem is that Price
does not have an Amount
property, but amount
, let’s change the way money instances are serialized, shall we?.
One can have total control over the serializátion of a type, by implementing a JsonConverter
.
Would a PascalCaseMoneyConverter
that writes pascalized properties and registering it for the IDocumentStore
at hand plus having a transformer returning pascalized properties do the trick?
It totally does!
Want to play with code? Go to the post’s repository and tear it apart.