Saturday, June 4, 2011

Observable Set Implementation for NHibernate

I want to guarantee that some defined actions always take place within my domain whenever an item is added to one of my domain entity collections (a many to one relationship).

Here's the setup

Assume we have the typical Order (parent) that contains OrderItems (children) entities.


    1 public class Order

    2 {

    3     private Iesi.Collections.Generic.ISet<OrderItem> orderItems;

    4 

    5     public Order()

    6     {

    7         this.orderItems = new HashedSet<OrderItem>();

    8     }

    9 

   10     public int Id { get; protected set; }

   11 

   12     public virtual IEnumerable<OrderItem> OrderItems

   13     {

   14         get { return this.orderItems; }

   15     }

   16 

   17     public void AddOrderItem()

   18     {

   19         this.orderItems.Add(new OrderItem());

   20     }

   21 }


As you can see, I don't expose the internal orderItems collection type. Instead I return the collection as an IEnumerable. This actually gives my domain some control over the collection as it effectively makes it immutable (only the collection, not the items in the collection). I like doing this and will probably continue but making the collection observable would give me some flexibility on this in the future.

What I'm interested in doing right now is just making it observable for internal usage as I have multiple methods within the Order entity that can add order items. If I can get the collection to be observable I could just register for the add event and do whatever needed to be done at that point.

Lets see some tests to show what I'm trying to do

    1 [Test]

    2 public void When_an_orderItem_is_added_an_one_internal_event_should_be_fired_off()

    3 {

    4     // Arrange

    5     var order = new Order();

    6     var numberOfTimesAddEventWasCalled = 0;

    7     ((INotifyCollectionChanged)order.OrderItems).CollectionChanged += (x, y) =>

    8     {

    9         if (y.Action.Equals(NotifyCollectionChangedAction.Add))

   10             numberOfTimesAddEventWasCalled++;

   11     };

   12 

   13     // Act

   14     order.AddOrderItem();

   15 

   16     // Assert

   17     Assert.AreEqual(1, numberOfTimesAddEventWasCalled);

   18 }

   19 

   20 [Test]

   21 public void When_an_orderItem_is_added_to_a_non_empty_Order_that_is_associated_to_a_session_one_internal_event_should_be_fired_off()

   22 {

   23     // Arrange

   24     var repo = new NHRepository<Order>(base.activeSession);

   25     var order = new Order();

   26     order.AddOrderItem();

   27 

   28     repo.Save(order);

   29     repo.Flush();

   30     repo.Clear();

   31 

   32     var fromDb = repo.Get(order.Id);

   33 

   34     var numberOfTimesAddEventWasCalled = 0;

   35     ((INotifyCollectionChanged)fromDb.OrderItems).CollectionChanged += (x, y) =>

   36     {

   37         if (y.Action.Equals(NotifyCollectionChangedAction.Add))

   38             numberOfTimesAddEventWasCalled++;

   39     };

   40 

   41     // Act

   42     fromDb.AddOrderItem();

   43 

   44     // Assert

   45     Assert.AreEqual(1, numberOfTimesAddEventWasCalled);

   46 }


Both of these test are not typical of tests that I usually write because they are way to knowledgeable and dependent on the implementation. These weren't developed as test first. I created them when I was trying to figure out how to implement this observable collection.

The first test is just there to verify that my solution worked without persistence. The second test was created to see how things worked when I actually applied persistence.

Onto the Implementation

In order to get this test to pass I created the ObservableHashSet. I came across this idea when I found this article by Gary DeReese. Specifically within the comments of that blog were references to this article by Adrian Alexander. Adrian created a project called "ObservableCollections" that I pretty much copied from (with some slight changes). Essentially, there are three new types that need to be created.

    1 public class ObservableHashedSet<T> : HashedSet<T>, System.Collections.Specialized.INotifyCollectionChanged

    2 {

    3     public event NotifyCollectionChangedEventHandler CollectionChanged;

    4 

    5     public override bool Add(T item)

    6     {

    7         var wasAdded = base.Add(item);

    8         if (wasAdded) invokeCollectionChanged(NotifyCollectionChangedAction.Add, item);

    9         return wasAdded;

   10     }

   11 

   12     public override bool Remove(T item)

   13     {

   14         var isChanged = base.Remove(item);

   15         if (isChanged) invokeCollectionChanged(NotifyCollectionChangedAction.Remove, item);

   16         return isChanged;

   17     }

   18 

   19     public override void Clear()

   20     {

   21         base.Clear();

   22         invokeCollectionChanged(NotifyCollectionChangedAction.Reset, null);

   23     }

   24 

   25     private void invokeCollectionChanged(NotifyCollectionChangedAction action, object changedItem)

   26     {

   27         if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));

   28     }

   29 }

The first type is the ObservableHashedSet that inherits from the Iesi.Collections.Generic.HashSet class and the System.Collections.Specialized.INotifyCollectionChanged inteface. I like to use sets for my NHibernate collections and the HashSet is the typical implementation to use. The INotifyCollectionChanged interface is what allows us to catch different events regarding the collection.

    1 public class ObservableHashedSetType<T> : NHibernate.UserTypes.IUserCollectionType

    2 {

    3     public object Instantiate(int anticipatedSize)

    4     {

    5         return new ObservableHashedSet<T>();

    6     }

    7 

    8     public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)

    9     {

   10         return new PersistentObservableSet<T>(session);

   11     }

   12 

   13     public IPersistentCollection Wrap(ISessionImplementor session, object collection)

   14     {

   15         return new PersistentObservableSet<T>(session, (ObservableHashedSet<T>)collection);

   16     }

   17 

   18     public bool Contains(object collection, object entity)

   19     {

   20         return ((ISet<T>)collection).Contains((T)entity);

   21     }

   22 

   23     public object IndexOf(object collection, object entity)

   24     {

   25         return -1;

   26     }

   27 

   28     public IEnumerable GetElements(object collection)

   29     {

   30         return (IEnumerable)collection;

   31     }

   32 

   33     public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)

   34     {

   35         var result = (ISet<T>)target;

   36         result.Clear();

   37         foreach (var item in ((IEnumerable)original))

   38         {

   39             result.Add((T) item);

   40         }

   41         return result;

   42     }

   43 }

The second type is the ObservableHashedSetType that inherits from the NHibernate.UserTypes.IUserCollectionType interface. This interface is NHibernates extension point for creating custom collection types (which is exactly what we are trying to do)

    1 public class PersistentObservableSet<T> : NHibernate.Collection.Generic.PersistentGenericSet<T>, INotifyCollectionChanged

    2 {

    3     public event NotifyCollectionChangedEventHandler CollectionChanged;

    4 

    5     public PersistentObservableSet(ISessionImplementor session) : base(session) { }

    6     public PersistentObservableSet(ISessionImplementor session, ISet<T> coll)

    7         : base(session, coll)

    8     {

    9         if (coll != null) ((INotifyCollectionChanged)coll).CollectionChanged += onCollectionChanged;

   10     }

   11 

   12     public override void BeforeInitialize(ICollectionPersister persister, int anticipatedSize)

   13     {

   14         base.BeforeInitialize(persister, anticipatedSize);

   15         ((INotifyCollectionChanged)gset).CollectionChanged += onCollectionChanged;

   16     }

   17 

   18     private void onCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)

   19     {

   20         if (CollectionChanged != null) CollectionChanged(this, args);

   21     }

   22 }

The third and final type is the PersistentObservableSet that also inherits from the INotifyCollectionChanged interface as well as from the NHibernate.Collection.Generic.PersistentGenericSet type. Truthfully I don't fully get the total gist of this. Obviously it has to do with persistence but... I would probably have to look into the source of NHibernate to understand this more. I probably should but not today.

After creating these types I was able to update the Order entity to use the new ObservableHashSet.

    1 public class Order

    2 {

    3     private Iesi.Collections.Generic.ISet<OrderItem> orderItems;

    4 

    5     public Order()

    6     {

    7         this.orderItems = new ObservableHashedSet<OrderItem>();

    8     }

    9 

   10     public int Id { get; protected set; }

   11 

   12     public virtual IEnumerable<OrderItem> OrderItems

   13     {

   14         get { return this.orderItems; }

   15         protected set

   16         {

   17             this.orderItems = (Iesi.Collections.Generic.ISet<OrderItem>)value;

   18             ((INotifyCollectionChanged)this.orderItems).CollectionChanged += orderItemChangedCallback;

   19         }

   20     }

   21 

   22     private void orderItemChangedCallback(object sender, NotifyCollectionChangedEventArgs e)

   23     {

   24         //

   25     }

   26 

   27     public void AddOrderItem()

   28     {

   29         this.orderItems.Add(new OrderItem());

   30     }

   31 }

There was just one more thing to do and that was create the mapping file. Seems like a lot of people are using Fluent NHibernate nowadays but I'm still old school and use hbm.xml mappings.

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

    2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Sample" namespace="Sample">

    3     <class name="Order">

    4         <id name="id" access="field">

    5             <generator class="hilo">

    6                 <param name="column">OrderNextHi</param>

    7                 <param name="max_lo">100</param>

    8             </generator>

    9         </id>

   10 

   11         <set name="OrderItems" table="OrderItem" inverse="true" cascade="all-delete-orphan"

   12              collection-type="Sample.Data.UserTypes.ObservableHashedSetType`1[[OrderItem]], TrustFundAEA.Data">

   13             <key column="OrderID" on-delete="cascade" />

   14             <one-to-many class="OrderItem" />

   15         </set>

   16     </class>

   17 </hibernate-mapping>

The Test Results

fails

Huh??? Why did the second test fail. It failed because the event fired off twice as I was only expecting it to fire once. Looking into this I see that after retrieving the Order entity from the db and once the lazy OrderItems is accessed, NHibernate fetches the persistent OrderItem and then ADDs it to the orderItems collection. Do you see where I'm going here? When the persisted OrderItem is added, the NotifyCollectionChangedAction event is fired off. Then when a new OrderItem is added the event is fired off again incrementing the numberOfTimesAddEventWasCalled variable both times. Well that was not what I was expecting. I'm only interested in the event when the entity itself adds to the orderItems collection, not when NHibernate does.

Getting this second test to pass proved to be a little difficult. I looked around Google and couldn't find a solution which surprised me because it seems to me that the typical usage would not want this event fired off when NHibernate is populating the collection.

I went looking for a solution within the PersistentObservableSet. I realized that the BeforeInitialize method was essentially the impetus for this unwanted event so I tried removing it with no luck. Doing so surprisingly resulted in the second test failing because no events were being fired off. My next attempt was to keep the BeforeInitialize method removed and add the AfterInitialize method with the same code as what was in the BeforeInitialize method. This seemed quite reasonable as I only wanted this event to be fired off after the entities had been initialized. Unfortunately this change still didn't fix the test as the events in test 2 still weren't being fired off. So it appears that the BeforeInitialize method must stay. Now I was really starting to think I should just open up NHibernate and try to track down another option but wanted to try one more thing.

    1 private void onCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)

    2 {

    3     if (CollectionChanged != null && base.WasInitialized) CollectionChanged(this, args);

    4 }

I'm not sure if this is the best way of handling this but guess what

passes

Well after all of this I've decided that I don't think this implementation is worth it if I'm only going to use it internally. Looking at long this took me to figure out, this conclusion stings a little. But at least I have a new tool that I can use in the future.