Wednesday, 17 March 2010

Using ironpython to generate nhibernate mapping files from a fluent configuration

I admit, this is a bit of a goldberg machine, but here's the context:

In my current project we need to store a bunch of events related to some 20 entities. Those events are the same for all entities, and they all need to be stored in the database. We use NHibernate to handle the "talk to the database" part, and fluent-nhibernate to configure the mappings via conventions for the most part. There are some entities which are either manually mapped or have specific overrides for particular cases (manually mapping some entities and automapping others is a mistake I think I'll avoid in the future).

For the most part this setup does wonders. We can add and change our entities and the mappings are generated without we thinking much of it. Since development speed has a larger priority than runtime speed in this project, we don't have much problems with the default mappings that are generated.

Except in the case of those events. Since the events are the same for all the entities, the event classes are open generics. Now, nhibernate can't map open generics, but can map closed generics, which is how we do it. We just iterate through all the types in the assembly that correspond to our entities and map those with fluent. The problem then becomes one of performance. As of now there are about 500 generated classes, and those take a while to discover and map. Since we're using fluent nhibernate to configure it in runtime, this means that each time the app is restarted there's a small downtime (on the scale of about 1 minute). While that's not much for an app in production, while developing one tends to restart the app a bit often. That's a problem in the day to day work.

I used our integration test suite to time the mapping, and on my pc it took about a minute to run. On the other hand, if we already have an .hbm file to feed to nhibernate, we can shave about 30 to 40 seconds. That's less than half the time for a simple trade-off of having to know when to generate the mappings.

To generate the mapping we could have created a new console project to just call the correct methods on our session provider factory, which would be a bit overkill. All we needed was to setup the environment and ask fluent for the mapping file.

And here is something that IronPython shines in.

Just create an .ipy file, add references to the correct assemblies, and since there's a clear separation of concerns (or so we hope), it's easy to configure fluent and ask it to generate the mappings for us. Most of the time you'll use .net code like you do in c# or vb.

There are only a couple of issues here:

- We need to remember to regenerate the hbm files when classes/mapping changes

- We need to remember to recompile both before and after regenerating hbms. Since we're loading the assemblies with ironpython outside of visual studio, the script doesn't know if the assemblies are stale or not.

Most of this can be avoided by having a build server rebuild the hbm files on its own and fail the build if they don't match with the latest in the source control. Of course, we could just have it commit the files, but that's one step I'm not fully comfortable with.

No comments: