In the last post we looked at using custom ID types to help abstract the column type from the domain.
This works well until you start trying to load and save entities using an ORM, as the ORM has not way to know how to map a column to a custom type. ORMs provide extension points to allow you to create these mappings. As I tend to favour using Dapper, we will go through setting it up to work with our custom ID types.
We need to be able to get the raw value out of the id type, but without exposing this to the outside world. To do this we internal interface:
Then update our id struct with a private implementation of the interface, and also mark the only constructor as internal:
We now can define a class which Dapper can use to do the mapping from uuid to id:
We then need to regiter the command with Dapper once on start up of our application:
Now when Dapper loads an object with a property type of
PersonID it will invoke the
Parse method on
PersonIDHandler, and populate the resulting object correctly. It will also work when getting a value from the
PersonID property, invoking the
SetValue method on
PersonIDHandler works, I really don't want to be creating essentially the same class over and over again for each ID type. We can fix this by using a generic id handler class, and some reflection magic.
We start off by creating a generic class for id handling:
The constructor of this class just finds a single constructor on our ID type with one argument, and creates a Func which will create an instance of the id passing in the value. We put all this constructor discovery logic into the
CustomHandler's constructor as this information only needs to be calculated once, and can then be used for every
We then need to write something to build an instance of this for each ID type in our system. As all of our IDs need to implement
IValueID to work, we can scan for all types in the assembly implementing this interface, and then operate on those.
This class first scans the assembly containing
IValueID for all types implementing
IValueID which are not abstract, and not interfaces themselves. It then goes through each of these types, and builds a new instance of
CustomHandler for each type, and registers it with Dapper.
You might notice this is in a class which implements
IApplicationStart - In most of my larger projects, I tend to have an interface like this, which defines a single
void Initialise(); method. Implementations of the interface get looked for on startup of the application, and their
Initialise method called once each.