Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Refactoring from Ayende's NHibernate.Generics to NHibernate 1.2

[Updated 4/3/07 - Added recommendation to move add/remove methods to a custom collection.] 

While using NHibernate 1.0x, developers who wanted support for generics often turned to Ayende's Hibernate.Generics.  Since the use of generics is now natively supported by NHibernate 1.2, you'll no longer need this extremely helpful utility.  Unfortunately, there's not a very simple way to migrate away from NHibernate.Generics without getting your hands a bit dirty, especially if you used the automatic wiring support for managing parent/child relationships.  The steps described below outline a general approach to upgrading from Ayende's NHibernate.Generics to NHibernate's native support for generics.  The entire process should take a couple hours to a couple days to complete, depending on the size of your project.  Furthermore, once you've taken the initial upgrade steps, you can migrate each parent/child relationship, one at a time, without having to do them all at once.

NHibernate.Generics was certainly cool in that it provided automatic wiring between parent/child relationships. So when a child was added to parent, the parent was automatically assigned to the child. This all happened with a bit of pixie magic which was great for the wow effect. But there's a lot to be said for being explicit, and, as Ayende has explained, losing the magic will be better for project maintenance in the long run. If you've had to troubleshoot these automatic relationships or bring a new developer up to speed, you probably already know this.

  1. Download latest software:
  2. Verify that your unit tests are running with all greens...fix that red one there! 
  3. Upgrade to NHibernate 1.2.  You'll most definitely run into a few migration issues. The official overview of the changes to the API may be found within the NHibernate 1.2 Migration Guide. Listed below are a few items that you should pay particular attention to:
    • Update all references to <hibernate-mapping xmlns="urn:nhibernate-mapping-2.0"> with urn:nhibernate-mapping-2.2. References may be found in HBM and config files.
    • All classes and collections with 1.2 are now loaded lazily by default. Consequently, if you run your application without modifying the lazy attribute, you'll most likely receive a number of "method x should be virtual." To resolve this, set lazy="false" for each class and collection that you do not want loaded lazily.
  4. Replace your existing references to NHibernate.Generics with the one compatible with NHibernate 1.2, found at the bottom of this post.

At this point, you should be able to get all your unit tests passing again and have the application behaving as it did before the upgrade.  Although the above steps are the only required steps for getting to NHibernate 1.2, it is highly recommended that you proceed with replacing all usages of Ayende's NHibernate.Generics with NHibernate's native support for generics.  The following steps allow you to refactor away from Ayende's NHibernate.Generics, one parent/child relationship at a time, without breaking much of the existing code.

As an example, assume you have a Customer class having a one-to-many relationships to Orders.  The Customer class, among other code, contains the following:

Customer.hbm.xml then contains:

The Order class, the "many" side of the relationship includes:

Finally, Order.hbm.xml contains:

In addition to the above code hiding complexity via automatic wiring, it also exposes the Orders list directly.  Essentially, the Orders collection may be directly modified with no enforceable business rules.  Suppose, for instance, that a Customer may only have 10 Orders for some reason.  Exposing the collection directly makes it very difficult to impose the collection-modifying business rule.  So during the refactoring process, we'll also want to improve collection encapsulation.

Another arguable smell in the code is that the parent/child relationship may be modified from either side.  This introduces additional maintenance, adds complexity to managing the relationship, and increases opportunities to introduce bugs.  Although we'll want to maintain the bi-directional traversability, we'll want to consolidate relationship management to the Customer side of the many-to-one relationship.  (As a side note, Jimmy Nilsson, in Applying Domain-Driven Design and Patterns, asserts that even bi-directional traversability should be avoided in general.  This is a point worth considering; but since we're focusing on migrating existing functionality, we'll keep the bi-directional traversability for now.)

To refactor the preceding code from Ayende's NHibernate.Generics to NHibernate 1.2 native support for generics with minimal breakage to existing code, repeat the following steps for each parent/child relationship:

  1. Create a new unit test to drive the add/remove functionality within the Customer class:
  2. To get the test passing, first add the following code to Customer: (Note that we're not removing any of the existing code yet.)
  3. Next modify/augment the Order class to reflect the following:
  4. Run your new unit test and watch it pass. You now have the business logic in place for managing the parent/child relationship. Now we'll move on to getting rid of the legacy code...
  5. Mark the legacy Customer.Orders collection as obsolete:
  6. Compile your application to view a listing of warnings showing you which parts of your code are using the deprecated collection. Get rid of the warnings, one by one, by modifying code to use OrdersTemp and the new methods AddOrder and RemoveOrder. You may have to add one or more additional methods to the Customer class to better accommodate the previous, direct exposure of the collection.
  7. Mark the legacy Order.OrderedBy property as obsolete:
  8. Compile your application to view a listing of warnings showing you which parts of your code are using the deprecated property. Get rid of the warnings, one by one, by modifying code to use OrderedByTemp. Since OrderedByTemp only has a getter, anywhere in your code which sets this property should be modified to use the parent Customer's AddOrder or RemoveOrder method, respectively.
  9. After compiling successfully, run only your business logic tests. Although NHibernate backed tests may still break at this point, all your business logic tests should be passing successfully.
  10. Remove the following code items from the Customer class: the reference to NHibernate.Generics, the obsolete Orders collection, the EntityList<Order> member, and the WireUpEntities method along with any call to this method. Remove the following code items from the Order class: the reference to NHibernate.Generics, the obsolete OrderedBy property, the EntityRef<Customer> member, and the WireUpEntities method along with any call to this method.
  11. Rename Customer.OrdersTemp to Customer.Orders. Rename OrderedByTemp to OrderedBy. All business logic tests should again be passing.
  12. Modify Customer.hbm.xml to reflect the following:
  13. Modify Order.hbm.xml to reflect the following:
  14. Run all your unit tests including NHibernate backed tests...with a bit of tweaking to account for various object life-cycle scenarios everything should be working. See the note below for additional items to be aware of.

Although it looks like a lot of work, the above represents a structured path for refactoring the code with minimal breakage to existing code. This is the way to go if you have a large development team and can only check out a few files at a time. You could certainly bypass a few steps and do "bulk" changes if you don't mind code being broken; but if you take that path, I would recommend not checking anything in until all your unit tests are passing.

Taking this a bit further...E.T., in the comments, noted that adding AddOrder/RemoveOrder to Customer makes the class less cohesive; besides managing itself, it's now managing the orders collection as well.  This could get a bit out of hand if additional collections are added to the Customer class.  To alleviate this and keep Customer more cohesive, consider creating a custom collection for managing orders and then move add/remove from Customer to this custom collection.  In this way, business rules may still be enforced without cluttering Customer with others' responsibilities.  You'll need to take some extra steps to bind NHibernate to a custom collection using IUserCollectionType.  Alternatively, if you don't need a full-blown cutom type, you can also simply create a collection wrapper to better encapsulate your collection related methods.  An example of doing this is found in the sample code at the NHibernate Best Practices article.

The simple example described in this post has not taken into account the variety of cascade options that are available nor various object life-cycle scenarios. So unfortunately, the above refactoring steps will most likely not account for every parent/child relationship in your code. Consequently, it's very important that you have a strong base of unit tests to ensure that your migration from NHibernate.Generics to NHibernate's support for generics has been completed successfully. As may be assumed, the NHibernate documentation is well written and is your best resource for dealing with the variety of situations that you will likely encounter. For each refactored parent/child relationship, it's good to test the following:

  • Do updates to a child, via the parent, still work?
  • Do creations of new children, added to the parent, persist to the database?
  • Do deletions of existing children from the parent, or by deleting the child directly, work correctly?
  • Have other CRUD, cascade scenarios been tested?

As always, your comments (corrections?) are most welcome. If you have a particular scenario which you're having trouble resolving using the sample above and the NHibernate documentation, I'd recommend posting your question to the NHibernate Forums which are generally very responsive.

Finally, I'm happy to say that I'll be releasing the 2nd edition of NHibernate Best Practices very soon which will demonstrate a number of relationship types using NHibernate 1.2.

Billy McCafferty


Posted 03-27-2007 12:46 PM by Billy McCafferty
Filed under: ,

[Advertisement]

Comments

Rob Eisenberg wrote re: Refactoring from Ayende's NHibernate.Generics to NHibernate 1.2
on 03-28-2007 8:35 AM

Awesome job Billy!

Eralp wrote re: Refactoring from Ayende's NHibernate.Generics to NHibernate 1.2
on 03-28-2007 5:03 PM

Thanks Billy. Couldn't have read your new article at a better time.

Jay R. Wren wrote re: Refactoring from Ayende's NHibernate.Generics to NHibernate 1.2
on 03-28-2007 5:05 PM

Wow. What a great example of real world changes, using tests to help you with the change, and refactoring while maintaining backward compatibility.

E.T. wrote re: Refactoring from Ayende's NHibernate.Generics to NHibernate 1.2
on 04-03-2007 12:39 PM

I don't see why you'd want to get rid of IList property. Replacing it with AddX()/RemoveX() methods is an unnecessery burden on the containing class. You are forgetting that your business class is not just a convinient way of getting data from a DB to the UI and back. Using your example, simply define CustomerOrders collection class that doesn't allow holding more than 10 items.

Keeping IList around has an additional benefit of working out-of-the-box with frameworks like Windows Forms and ASP.NET. That's the definition of cohesive I believe.

Cheers,

E.

Billy McCafferty wrote re: Refactoring from Ayende's NHibernate.Generics to NHibernate 1.2
on 04-03-2007 12:54 PM

I agree that exposing IList would keep it more cohesive...but not encapsulated.  I also agree that defining a custom CustomerOrders collection class would be an even better way to go.  My order of preference would be:  custom collection, Add/Remove methods on containing class, and then directly exposed IList.  Directly exposing the underlying IList is akin to making the private member public.  It's convenient and keeps overhead to a minimum, but also opens itself to misuse.  On the other hand, that may not be an issue if there are no limitations on the collection and it's OK to add nulls to it, to clear it, adding children to it which point to a different parent, etc.  But that's not often the case.

WIth that said, you've prompted me to add a note to the post, recommending to carry the add/remove methods to a custom collection, which is a good idea to emphasize.

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)