Tuesday, January 17, 2012

hbm2net - c# instead of T4

I'm still mapping my entities to NHibernate by using hbm files. I still do this for several reasons, but i won't detail them now.

Since I'm using hbm files, I want to exploit one of their huge advantage - auto generating lot's of code that can be derived from the hbm files by using the hbm2net tool. 

for example:

- Auto generate my POCO entities.
- Auto generate my DTO entities.
- Auto generate my server side validations and their equivalent client side validations
- Implementing cross cutting behaviors like overriding GetHashCode(), Equals(), or invoking      NotifyPropertyChanged etc.

In it's earliest versions, hbm2net was expecting a Velocity script to auto generate code. Recent versions can also work with T4 scripts.
hbm2net is great and it's hard to imagine how to work with out it. Unfortunately, Velocity is not that user-friendly and neither T4.

My preferred way is to implement my own generator written in c#, which can be plugged into hbm2net instead of working with T4 or Velocity.

And why would i prefer c#...? Well, i guess that's obvious...

So, lets get to work.

First of all, download the latest version of hbm2net from here and extract it to wherever you like.

Next, create a Class Library project in Visual Studio and call it MyHbm2NetGenerator. 
Add a reference to NHibernate.Tool.hbm2net.dll (should be located where you've extracted the zip file).
Add a class to this project and call it POCOGenerator. This class should be derived from NHibernate.Tool.hbm2net.AbstractRenderer and implement NHibernate.Tool.hbm2net.ICanProvideStream:

the hbm2net will create a single instance of this class and will use it to generate the derived code for all hbm files.

Next, implement  ICanProvideStream.CheckIfSourceIsNewer:

The hbm2net will invoke CheckIfSourceIsNewer for each hbm file. The source parameter will be the LastWriteTimeUtc of the current hbm file. The directory parameter will be the path for the output directory in which the generated files will be stored. This method should return true if source is greater than the LastWriteTimeUtc of the generated file, meaning - if there were changes in the hbm file since the last generation of the POCO file.

The method GetFileName is receiving the parameter clazz which is holding almost all the details about the POCO entity that is going to be generated. I'll give more details about this class soon, but for now, all we need in this method is the POCO entity name which can be found at clazz.GeneratedName.

Next, implement  ICanProvideStream.GetStream:

The hbm2net will invoke this method to get a stream to flush the content of the current generated POCO entity.

Next, you need to override the method Render. This is actually the main method where you generate the content of the POCO entity and flush it (save it).

Now, implement a method that will generate the POCO's content (GeneratePOCO is the name i gave it).
Of course you should be using  the ClassMapping object to get all the POCO's details e.g. class name, class modifiers, base class, properties, fields etc.
In the next post i will show you in more details what can be done with ClassMapping in order to generate the desired POCO content.

OK, we're getting there: compile your MyHbm2NetGenerator project and then copy MyHbm2NetGenerator.dll to the directory where you've extracted hbm2net.

Next, create an xml file, call it config.xml (or whatever...) and put it wherever you like. config.xml should look like this:

renderer is the FullyQualifiedName of your POCOGenerator class. package is the namespace for your POCO entities - you will receive it in the POCOGenerator.Render as the savedToPackage parameter.

Now, execute the following command in the command shell:

<hbm2net dir>\hbm2net.exe --config=<config dir>\config.xml --output=<output dir> <hbm files dir>\*.hbm.xml

And that's it! Go to <output dir> to see your generated files.

To make hbm2net auto generate your code on every build of your domain/DTO/validations project, you can make a pre/post build event in your project settings with the command line I just showed you.

download code example