Monday, September 29, 2008

Lessons from My First NHibernate Project

Part2, Part3

I recently used NHibernate for the first time. As a result, I learned a number of things... some obvious, some not so obvious... that I thought might be helpful to other people coming to NHibernate for the first time. This is the first article of a three-part feature. I originally planned to post them all as one large article, but life interfered and it's now three weeks after I had started this article and I've decided to split it up so I can get part of it out there now while I finish the rest.

This first article briefly describes my experience getting started with NHibernate. The following two articles describe the things I think I might do differently next time now that I know a bit more about NHibernate, and the various tips and tricks I've learned so far along the way. There are a couple aspects of NHibernate that I felt are especially tricky for new users. I've mentioned those in this series of articles, but I'm planning to write a separate article covering each of those in more detail as well.

None of these articles are step-by-step tutorials on configuring or using NHibernate. I found that the documentation on NHibernate's site is very useful for that and writing an article on that would be redundant. Also, there are several articles on CodeProject that are quite useful for programmers getting started with NHibernate for the first time. This series of articles is more a listing of tips, tricks, and traps to avoid that I thought would be a useful for programmers after they've gone through NHibernate's Quick Start Guide.

The Learning Curve

When it came time to start developing the data layer of my current work project, I decided to try NHibernate instead of coding yet another data layer myself in ADO.NET hoping to (1) save some development time, and (2) learn a new tool. Being new to NHibernate and a bit daunted by all the XML files required by it, I also decided to try NConstruct (the free lite version) to automate the generation of the initial classes and XML files. I seriously considered ActiveRecord from Castle Project since that replaces NHibernate's config files with attribute tags on the classes and properties, but finally decided against it for a couple reasons:
  • ActiveRecord doesn't support stored procedures, and at the time I was planning to use stored procedures (I didn't realized at the time that although NHibernate does now support stored procedures, its implementation is rather clunky to say the least. Also, after quite a bit of research, I discovered that many of my reasons for using stored procedures weren't totally valid and I ended up doing the project without them).
  • By using attributes, entity classes defined using ActiveRecord are not true POCO (Plain-Old CLR Objects). A true POCO should not include anything in its implementation that ties it to a data source or service. At its most basic, an entity POCO classes would consist of little more than one or more constructors and a property for each column in the database table being modelled. NHibernate follows this contract by placing the mapping information between the class and table in a separate XML file. ActiveRecord breaks this contract slightly by putting the mapping information inside the class itself.

NConstruct worked fairly well for me, but I quickly discovered a few big drawbacks. The main drawback is that you cannot use NConstruct to modify its own generated NHibernate classes and mapping files if your database changes. Since the application and database are being developed in parallel, I'm occasionally making changes to the database structure as I start developing features that require additional tables or refine the design. However, if your database structure changes you need to either:
  • run NConstruct again and completely replace the existing classes and mapping files
  • create a new NConstruct project and then manually move the changed classes and mapping files into your existing project
  • bite the bullet and learn to make the changes in the maping files and code yourself
I decided to go with the last option.

Like any new technology or API, there's always a learning curve when you first try to use it. NHibernate's certainly no exception. While I'm convinced I made the right decision, the ride has not been the easiest, and I've wondered more than once if I actually saved much time by using it rather than writing the ADO.NET code myself. I think I have... especially since you get a lot of things for "free" with NHibernate such as session management and transactions that normally suck up a lot of development time to get right when added into a basic data layer. However, it's taken a bit more effort to get there than I had originally planned. But now that I have one NHibernate project under my belt I believe that I'll be able to save a considerable amount of time developing data layers for future projects.

False Starts

I'll readily admit my first NHibernate data layer is not without its share of mistakes. For starters, since NConstruct doesn't generate interfaces, its code makes a lot of direct references to other entity classes (modelling parent-child relationships, for example). I extracted interfaces for all of the generated classes, but because of the way the original code was generated, my interfaces are a bit of a mess and NHibernate isn't using them (and since I knew almost nothing about NHibernate when I started, I didn't know how to change that). I'm using the interfaces instead of the classes outside the data layer, but again because of the way the code was generated, I've had to do a lot of casting between interfaces and concreate classes. Working under a time crunch, I originally found it easier to just do the casting, but I really need to go back and clean that up!

The second problem I discovered is that I needed to do a lot of changes to the auto-generated code, which is basically means that that code can never be auto-generated again! (thus my decision to write any changes to the mappings myself instead of trying to regenerate them using NConstruct). The main reasons for this are:
  • Being new to both NHibernate and NConstruct, I made some mistakes when using NConstruct's wizard that I didn't discover right away
  • NConstruct couldn't do everything I wanted it to do (they've since changed some of that)
  • I don't code the same way the makers of NConstruct do
An example of the first situation is my lack of understanding of how NHibernate handles creating sub-classes and NConstruct's implementation of that. As a result, instead of creating true subclasses for the types of Documents in the system (all of which derive several basic properties from a base Document class and table), I ended up having to encapsulate the base document class inside the classes of documents that derive from it... an implementation issue that rears its ugly head from time to time. Next time I'll know better and be able to do this the correct way.

I also found that I needed to go back through the mapping files and classes and change all of the property names. By default, NConstruct generates property and field names that are identical to the column names in the database table. Since our DBA prefixes the table name to all column names, the property names generated were less than friendly on the fingers (for example, my "FieldDefinition" class had properties like "FieldDefinitionName" and "FieldDefinitionIsEditable" where I wanted the entity class' properties to be simply "Name" and "IsEditable").

Finally, my database included quite a few many-to-many linking tables (for example, a User can belong to multiple Groups and each Group can contain multiple Users). Instead of creating a many-to-many set, NConstruct created a separate class for the linking table. Maybe that was due to my lack of knowledge about NConstruct and NHibernate, but I found it extremely annoying to correct.

On the plus side, all of these issues brought me up to speed on NHibernate mapping files very quickly!

Now, it may sound like I'm knocking NConstruct quite a bit. Not so. I think it's a very nice application... it just has a few rough edges. Also, like I mentioned earlier, my programming style is a bit different than NConstruct's programmers so I had to redo several things to make the generated classes work for me. I can't reasonably expect them to generate code that can fit everyone's preferences exactly, though, so I don't have a complaint with that. Even with all of the extra work NConstruct still probably got me up and running with NHibernate much faster than if I had to create the mapping files and classes completely on my own.