[Update on Dec. 17, 2007: Removed extraneous CustomCollectionsPro.Core.CollectionInterfaces namespace and fixed a bug in CustomCollectionsPro.Data.Collections.PersistentProducts: the second parameter of the PersistentProducts constructor must take a generic IList<Product> instead of the Products custom collection which was there previously; otherwise, you cannot create and save a new Category object. Thank you to Uwe Lesta for suggesting these improvements.
Update on Feb. 14, 2008: Pete Weissbrod made note that the PersistentListType class should instantiate a POCO .NET List instead of a custom list which was problematic in some instances. (See Pete's comment below for a more detailed explanation.) The upshot to this is that it reduces the number of generic qualifiers in the HBM, provided to PersistentListType, by one. Nice catch Pete!]
Download the source code for this post.
In part I and part II of this series, we established our goals for wanting clean custom collections in the domain and looked at a solution for doing just that. The drawback to the presented solution was that the "type" class, needed to inform NHibernate how to manage the custom collection of our choosing, was not reusable. Consequently, any new custom collections that we add would also need a unique type class being almost identical to the other type classes. That's just not cool. So why didn't I just skip showing that and get right to the reusable solution?
In order to support a more reusable solution, the underlying framework enables increased flexibility at the expense of added complexity. Fortunately, it's complexity that we never need to look at. The NHibernate assembly makes the development of data-access a breeze...that doesn't mean I need or want to be digging around the NHibernate source code on a daily basis. So let's take a look at what's been refactored...
The first thing to note is that a coupe of new projects have been added to hold the reusable components of our solution and to demonstrate our working solution in a unit test scenario. The general architecture is now closer to my default architecture (sans the Presenters layer) and has a better separation of concerns.
The CustomCollectionsPro.Core Class Library
Because we had already gone through the process of isolating the data-centric collection management out of the domain layer, almost nothing changed in this code whatsoever. In fact, besides moving the Design-by-Contract framework to a reusable library called ProjectBase.Core, the only change to the domain layer was a modification to Category.hbm.xml...and it's not a pretty change at that. Our collection type declaration now looks like the following:
[CustomCollectionsPro.Data.Collections.PersistentProducts, CustomCollectionsPro.Data]], ProjectBase.Data"
I told you it wasn't pretty. This is the second drawback to the generalized solution. The first is a more complicated, generic, collection type class (that we rarely have to look at); the second is the HBM mapping required to communicate with the generic type class. This mapping is passing two object types as generic-class specifications: the domain object that the custom collection contains and the custom collection equivalent from the data layer which NHibernate uses.
The CustomCollectionsPro.Data Class Library
To make this solution more reusable, the following classes have been moved out of the CustomCollectionsPro.Data class library into a new one called ProjectBase.Data: NHibernateDao.cs, NHibernateSessionManager.cs. The PersistentProductsType.cs has been dropped altogether in favor of the generic alternative, found in ProjectBase.Data, that we'll look at next. That's all the changes here...it's just been simplified.
The ProjectBase.Data Class Library
The interesting class here is /NHibernate/Collections/PersistentListType.cs. This class accepts the generic specifications given to it from the HBM mapping in the domain layer to enable a generic, collection type class. It looks similar to the old PersistentProductsType.cs, but now it's using reflection to get an
IPersistentCollection from an
ISessionImplementor which is compatible with our custom collection.
The CustomCollectionsPro.Tests Class Library
This is here to show that the solution works equally well outside of a web context...and, besides, having unit tests is a good thing, right? To get it working locally, be sure to change the connection settings within the NHibernate configuration file under the bin directory.
Creating Your Own Custom Collections
With everything in place, adding a new, custom collection which is able to be persisted by NHibernate is just a few simple steps away:
Create a unit test describing your new custom collection as a collection interface that you'll be working with. For an example, see CustomCollectionsPro.Tests/CategoryDaoTests.cs. The example unit tests kills two birds with one stone by testing both the domain custom collection (by calling the SortByName method) and the NHibernate aware custom collection. I would typically split these tests in two so that each test has only one concern. In that way, it's easy to turn off your database tests while still being able to run all of your domain layer tests.
Create the interface for your custom collection within your domain layer. For example, see CustomCollectionsPro.Core/CollectionInterfaces/IProducts.cs.
Create the domain collection which implements this interface along with the NHibernate aware, data collection in the data layer which passes on method calls to the domain layer; e.g., CustomCollectionsPro.Core/Products.cs and CustomCollectionsPro.Data/Collections/PersistentProducts.cs.
Create your association from a containing class to your collection interface and complete the wiring via the HBM; e.g., CustomCollectionsPro.Core/Category is the container class pointing to the products collection via
IProducts and CustomCollectionsPro.Core/Category.hbm.xml shows the corresponding HBM wiring.
It's much less tedious than it sounds and allows you to have full control over your custom collections while keeping your domain layer clean of any pesky, data-layer references. I've tested this out pretty thoroughly, but please let me know if you encounter any problems or find a way to simplify the declaration within the HBM mapping file.
In the next and final part, I'll show a much simpler alternative using extension methods which is quite handy if you're on .NET 3.0 or later.
12-07-2007 9:10 AM