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
Custom Collections with NHibernate, Part III: Refactored

[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:

collection-type="ProjectBase.Data.NHibernate.Collections.PersistentListType`2[ [CustomCollectionsPro.Core.Product, CustomCollectionsPro.Core], [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:

  1. 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.
  2. Create the interface for your custom collection within your domain layer.  For example, see CustomCollectionsPro.Core/CollectionInterfaces/IProducts.cs.
  3. 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.
  4. 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.

Billy McCafferty


Posted 12-07-2007 9:10 AM by Billy McCafferty
Filed under:

[Advertisement]

Comments

Pete w wrote re: Custom Collections with NHibernate, Part III: Refactored
on 12-07-2007 2:28 PM

I like this as much as your "nHibernate best practices" article.

My code was simpler by having a single DomainCollection<T, IdT> where T: DomainObject<IdT>, but then I lost the ability to have truly custom collections with custom intelligence.

To me, the PersistentListType class is magic, I never could have thought that one up.

I only have one concern: it seems as your custom collections get "smarter", there is a higher chance of duplicating code between your "transient collection class" and your "persistent collection class"...

Jacques Philip wrote re: Custom Collections with NHibernate, Part III: Refactored
on 12-07-2007 8:18 PM

In your sample, the custom functionality (sorting) of the collection does not do anything on the database server, so I don't understand why it should be handled by a class depending on NHibernate at all.

In .NET 3.5, could not we do it with extension methods, in this case:

category.Products.OrderBy(p => p.Name)

where Products is a normal List<Product>, or a custom extension method if it is something Linq does not handle?

Billy McCafferty wrote re: Custom Collections with NHibernate, Part III: Refactored
on 12-08-2007 10:40 AM

Jacques,

You're correct in noting that the sorting method was a weak example of why you'd want to create a custom collection.  There are far better reasons for going through the trouble of making a custom collection.

A better example would be an accounting program that has a custom collection holding a number of ledger entries.  It would be appropriate to add a number of accounting methods directly to the collection of ledger entries for totalling in various ways, filtering with specification patterns without having to go back to the database, and using the visitor pattern with the collection to produce a number of report outputs.

Custom Collections with NHibernate, Part II: The Basics - Billy McCafferty wrote Custom Collections with NHibernate, Part II: The Basics - Billy McCafferty
on 12-17-2007 2:17 PM

Pingback from  Custom Collections with NHibernate, Part II: The Basics - Billy McCafferty

Billy McCafferty wrote Custom Collections with NHibernate, Part I: Who Cares?
on 12-17-2007 2:18 PM

When people speak of the object-relational impedance mismatch , they usually talk about the differences

Billy McCafferty wrote re: Custom Collections with NHibernate, Part III: Refactored
on 01-02-2008 6:43 PM

Posted by Maciek @ http://mgrzyb.blogspot.com/

I've been using custom collections decorating IList<T> for some time now, and many times the biggest advantage of having a custom ones instead of generic IList<T> was that I could expose a limited interface (most of the time immutable facade) of the collection trough a public property, while having a full interface available as private field.

I like your solution and will try it out ASAP... if only there was a way to make the mapping file pretty :P

Gerke Geurts wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-08-2008 6:41 PM

I am wondering whether the need for custom has reduced significantly by the availability of extension methods in the last version of the .NET Framework? Rather than coding the custom methods on the collections you would code an extension on for example IList<Product>. In that case a good set of (ideally observable) base collections for Set, List and Dictionary would be all you need.

Billy McCafferty wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-08-2008 6:53 PM

The more I learn about 3.5, the more I agree with you.  Creating NHibernate custom collections involves a lot of overhead.  80% of the time, the custom methods are simply there to provide advanced querying and manipulation of that collection which could be easily abstracted into extension methods and LINQ operations.

With those two options, the only reason that custom collections would now be warranted would be for handling add/remove specific handling logic.  Perhaps even that could be abstracted.

I'm currently up to my neck in R&D for the start of my next large scale project.  Your suggestion is one of the avenues that I'm researching for how to best implement custom collections with 3.5.

Gerke Geurts wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-08-2008 7:49 PM

I am currently inclined to implement a few custom collections (set, bag, dictionary) that support interception of events that change the collections. That would facilitate the implementation of repetitive sync logic for bidirectional associations.

I am hesitant to immediately implement the rather arcane ICollectionChangeNotification. A simple custom interface that supports the plugging in of interceptors appeals more to me. One such interceptor could then be an adapter to ICollectionChangedNotification.

Gerke Geurts wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-08-2008 7:53 PM

And another comment, this time a small suggestion: One way to reduce the number of generic type parameters from 3 to 2 on your current solution would be to provide the type of the domaincollection as a property or attribute on the nhibernatedatacollection.

Gerke Geurts wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-08-2008 7:56 PM

And the last one: Has anyone tried to use XML entity definitions in NHibernate mapping files to reduce the repetitive typing of for example .NET namespaces and assembly names? That should reduce mapping file sizes considerably.

Billy McCafferty wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-08-2008 10:19 PM

Not that I'm aware of...if you give it a try, please send me an example.

Gerke Geurts wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-11-2008 11:03 AM

Using entity definitions in NHibernate mappings works indeed. Here's an example:

<?xml version="1.0" encoding="utf-8" ?>

<!DOCTYPE hibernate-mapping [

 <!ENTITY BEGIN_DOMAIN_TYPE "Agg.BusinessObjects.Persistence.UnitTests.Mocks.">

 <!ENTITY END_DOMAIN_TYPE ", Agg.BusinessObjects.Persistence.UnitTests">

 <!ENTITY BEGIN_SET_TYPE "Agg.BusinessObjects.Persistence.Nh.UserTypes.PersistentEntitySet`1[[">

 <!ENTITY END_SET_TYPE "]], Agg.BusinessObjects.Persistence.Nh">

]>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" schema="dbo">

 <class name="&BEGIN_DOMAIN_TYPE;MockUserEntity&END_DOMAIN_TYPE;" table="users" lazy="false">

   <id name="UserId" column="user_id" type="String" length="20">

     <generator class="assigned" />

   </id>

   <property name="FirstName" column="first_name" type="String" length="50" not-null="true" />

   <property name="LastName" column="last_name" type="String" length="50" not-null="true" />

   <many-to-one name="Parent" column="parent_id" class="&BEGIN_DOMAIN_TYPE;MockUserEntity&END_DOMAIN_TYPE;" cascade="save-update" />

   <set name="Children" inverse="true" collection-type="&BEGIN_SET_TYPE;&BEGIN_DOMAIN_TYPE;IMockUserEntity&END_DOMAIN_TYPE;&END_SET_TYPE;">

     <key column="parent_id" />

     <one-to-many class="MockUserEntity" />

   </set>

 </class>

</hibernate-mapping>

Pete w wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-13-2008 4:23 PM

I discovered an interesting bug in my custom collections today, it seems to be in yours too, since I borrowed from your ideas.

This may be benign in your case, but it was a very ugly bug to me. I was working on a collection that generates events for BeforeAdd, Added, and AfterAdd, such that subscribers could observe and veto changes to the collection. I also added intelligence to my collection to prevent the same reference ever added more than once (uniqueness).

The problem is in your PersistentListType class: The instantiate method:

public object Instantiate() {

           return new TypeOfDomainCollection();

       }

should be:

public object Instantiate() {

           return new List<TypeOfCollectionItem>();

       }

Why? Your persistent collection inherits from PersistentGenericBag. In the NHibernate source code, you will see a BeforeInitialize(..) method that will call PersistentListType::Instantiate(). The PersistentGenericBag depends on Instantiate to build its inner bag.

Instantiate should not return the intelligent collection, it should return the "dumb" collection that the intelligent collection is wrapping around.

If you return an intelligent collection in the Instantiate method, then what you end up with is a Persistent Intelligent collection that encapsulates a transient intelligent collection. (the persistent intelligent collection should encapsulate a dumb collection, that's why the persistent collection has the same methods as the dumb collection).

Again, this may be benign to you, but it was a sobering discovery to me.

Billy McCafferty wrote re: Custom Collections with NHibernate, Part III: Refactored
on 02-14-2008 6:31 PM

Great find Pete!!

Billy McCafferty wrote Custom Collections with NHibernate, Part IV: Extensions!
on 09-03-2008 7:16 PM

In part I of this series, we examined motivations for maintaining custom collections that are compliant

Community Blogs wrote Custom Collections with NHibernate, Part IV: Extensions!
on 09-03-2008 7:53 PM

In part I of this series, we examined motivations for maintaining custom collections that are compliant

Adal Colu wrote re: Custom Collections with NHibernate, Part III: Refactored
on 04-15-2009 7:59 PM

Billy,

I'd like to know nhibernate custom collections you introduced but source code download is unavailabe from this page. Where could I download this one.

credit repair nh wrote credit repair nh
on 12-25-2009 9:42 PM

I only wish that I had found this website sooner!

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)