Bridge the Gap Between Your NHibernate Collections and WPF UI
While working on a pet software development project of mine, I needed to bind the entity collections I retrieve from my NHibernate back-end to the WPF (Avalon) list controls in my application UI. Since the default
Fortunately, all is not lost. This whiz-bang feature can be leveraged with an NHibernate data layer if you are willing to get your keyboard and mouse fingers a bit dirty. NHibernate 1.2 provides the IUserCollectionType interface so that a custom collection type can be developed and referenced when defining a collection in your entity mapping configuration. This means that a new collection class can be defined which implements this interface combined with INotifyCollectionChanged to create an observable collection which will auto-magically update the display of any WPF list control!
Getting custom collections which are usable by NHibernate is a bit tricky at first, so I've included a project which demonstrates how I accomplished it. A few simple domain model entities are mapped to DB tables using NHibernate, one of which utilizes a custom collection. It can be bound to a WPF list control and take advantage of all the nice databinding features. The custom collection also supports lazy-loading as long as you have 'prox-ified' your domain entity classes by declaring all the public properties and methods as virtual, which is the typical procedure as described in the NHibernate documentation.
You can download the demo project here. It contains the source code for the custom persistent and transient collection classes. I've also provided a class diagram which outlines my approach to the solution. There is also an WPF UI project that provides some basic functions to play with the collection contents. You can observe how the UI reacts to those changes without any user-written code to do so.
NOTES:
You will need the latest NHibernate 1.2.0, .NET 3.0 Framework and SQL Server 2005 Express installed to use this project. Remember to change the absolute path of the database in the hibernate.cfg.xml for your local machine. This file is located in the Library project. This post assumes that you're already familiar with the basics of both NHibernate and WPF technologies; surf to their respective websites for more information on getting started.
21 comments:
awesome ! I think this can help with complicated databinding scenarios in windows forms 2.0 too.
Looking forward to dissecting it :-)
Good job!!
Thank you very much for this. I was about embark on this exercise myself and found your link on NHibernate forums. I appreiciate your posting it.
Thanks a lot for sharing this! Very useful!
Excellent :) Thanks for this!
Is it possible to get one of these ObjservableBags as the results of a query?
Thanks for a great article. This works great for collections, but how can I implement "INotifyCollectionChanged" when returning a set of records from a query. For example, "IList<> = _session.CreateCriteria(typeof()).List<>(); How could I get this to return an IObservableList<> subclass?" Thanks in advance for any comments.
Excellent work. However you realize your solution will not support lazy collections?
I am assuming you are as you explicitly set lazy to false in the mapping.
I have corrected this and am providing how to get the code below.
NOTE: I used this before so I do not have the observable interface but it would be trivial for you to add this for your article. If it serves you to repost with this option to allow Laziness this is open source freeware I created so go ahead.
For me, missing Lazy collections is a deal breaker. The cool thing is you can use this class over and over. I did the hard part already.
Here is the trick:
1) As you illustrated to have any kind of custom persistable collection you must inherit from PersistentGenericBag<T> which has no empty constructors (therefore you cannot use this to inherit on your main collection (!) as you MUST have an empty constructor for NHibernate to use).
2) As you show, we want our main custom collection to inherit from the concrete List<T> to use its core services as ouor base so you have already used your 1 concrete parent. (if we had multiple inheritance we could inherit from both, problem solved - kind of - almost - well another day.....).
3) Our interface must inherit from multiple sources for casting to work as we need it to:
a) It must inherit from IList<T> for obvious reasons.
b) ALSO....(I see people report this as an error fairly often) for casting purposes, we need to be able to cast our item to PersistentGenericBag<T> so we must also inherit from the same interface.
YIKES!!!!!!! PersistentGenericBag<T> HAS NO INTERFACE we can use!
Solution: Extract the interface (I live and die by RerSharper)...Fake out the system by using a class which both inherits from PersistentGenericBag<T> and implements the extracted interface. Then only refer to our 'wrapper' class.
So there are 3 files:
1) internal class DomainPersistentGenericBag<T> : PersistentGenericBag<T>, IDomainList<T>
2) public interface IDomainList<T> : IList<T>
NOTE: This interface contains every single contract from DomainPersistentGenericBag<T> and a 'new' prefix on items where we override the IList<T>'s method.
NOTE2: I started by calling this IDomainPersistentGenericBag. This is perhaps more 'correct' but not as easy for the developers to sync the Interface and the concrete class as they do with IList and List (you'll see).
3) Finally our main Collection class, which we use all over the place quite easily:
public class DomainList<T> : List<T>, IUserCollectionType, IDomainList<T>
All the code is downloable from the blog below....
To use this simply:
1) In your Domain Class (I never use mapping files now, only attributes FYI):
public class MyDomainClass() {
...
private IDomainList<ArticleZones> _Zones;
[Bag(0, Inverse = true, CollectionTypeType = typeof(DomainList<ArticleZones>), Lazy = true, Generic = true, BatchSize = 10)]
[Key(1, Column = "articleid")]
[OneToMany(2, Class = "PMSiteDomainModel.ArticleZones, PMSiteDomainModel")]
public virtual IDomainList<ArticleZones> Zones
{
get { return _Zones; }
protected set { _Zones = value; }
}
Works like a charm!
You can refer to this collection as:
IDomainList<T>
ISet<T>
DomainList<T>
All work and you get full customization!
To download, this will shortly be up on my blog at:
damon.agilefactor.com
Thanks,
Damon Carr, CTO
agilefactor
> Is it possible to get one of these ObservableBags as the results of a query?
As far as I know, you cannot return a custom collection type from a query.
However, since the query methods return a generic List, you can wrap the query result in a generic ObservableCollection, which is the built-in collection class for WPF that implements the INotifyCollectionChanged interface.
> Excellent work. However you
realize your solution will not support lazy collections?
It does support lazy collections. I have modified my example project to utilize lazy-loading by making the necessary changes to my NHibernate mapping XML.
I also inserted a few Windows dialogs that display a message verifying the child collection was not loaded with the parent. You can view the source code to see how I am verifying that.
Take a look and let me know if you concur.
I, to, would like to thank you for sharing this. I may have gotten lost if I had to venture into doing this myself. Building the project went mostly smooth. I had one slight issue getting it to run. That is, I had to add references to the Castle.DynamicProxy, Iesi.Collections, and log4net DLL files myself. Perhaps you should include these in your download?
Thank you again for your contribution. I made some changes to my local copy, and I'm wondering if you'd be interested in seeing them. I prefer that clients deal directly with the ListContainer.Items property, rather than using auxiliary methods. So I deleted the AddListItem and RemoveListItem methods from ListContainer. I subscribe to ListContainer.Items's CollectionChanged event in the ListContainer.Items setter. Then when an item is added, the delegate executes the "item.Container = this" logic that was previously in ListContainer.AddListItem. Doing it this way needs about the same amount of code, but the advantage is that ListContainer has a cleaner interface that's less likely to be accidentally misused. Email me and I'll send you the modified code files.
> I prefer that clients deal directly with the ListContainer.Items property, rather than using auxiliary methods. So I deleted the AddListItem and RemoveListItem methods from ListContainer.
The AddListItem/RemoveListItem methods are not necessary; I included them as methods that wrap all the necessary logic to suit the particular NHibernate mapping (ex: set parent property on child item, then add child item to parent collection). They are by no means required, and your approach is definitely valid - I prefer the more simple interface as well.
The point of my example wasn’t so much the design of the ancillary example entities (ListContainer/ListItem classes) but the design of the custom NHibernate collection class and how to effectively hook that up with WPF.
Very helpful. Thanks a bunch Gary.
Do you know about the ObservableCollection<T> class? It's located in the System.Collections.ObjectModel namespace. By making your ObservableList<T> class derive from ObservableCollection<T> instead of List<T>, I could eliminate 99 lines of code out of 169 from ObservableList.cs.
That is, I eliminated everything other than the IUserCollectionType Members. Your library compiled fine after making the change, except I had to change one call to OnCollectionReset() to instead call ClearItems(). The example app appears to be running alright.
I feel uncomfortable hiding base class members with the 'new' keyword instead of overriding virtual ones. I worry that if the reference variable is of a base type then an incorrect method will get called. This is what lead me to make the above changed. At first I was going to use Collection<T>, but ObservableList<T> derives from Collection<T> and also implements INotifyCollectionChanged. For an explanation about why Collection<T> is a better base class than List<T> see here. As for your PersistentGenericObservableBag<T> class, unfortunately I cannot make the same easy change since it derives from NHibernate's PersistentGenericBag<T> class.
Today I came across a post in the NHibernate forum in which a Hibernate Team member said "the IUserCollectionType should be implemented by a separate object, not by your collection class". I've already eliminated everything from your ObservableList<T> class other than the IUserCollectionType Members. So today I removed both the List<T> base class and IObservableList<T> interface implementation, and now it just implements IUserCollectionType. I then renamed it to "ObservableListType<T>" to reflect this new status. I then modified the methods of ObservableListType<T> by replacing occurrences of IObservableList<T> with IList<T>, and ObservableList<T> with ObservableCollection<T>. In PersistentGenericObservableBag<T>, instead of implementing IObservableList<T> it now implements both IList<T> and INotifyCollectionChanged. As for the ListContainer client, it's private "_items" field and public "Items" property are now of type IList<ListItem> instead of IObservableList<ListItem>; they can then be set to an instance of ObservableCollection<ListItem>. After all that, I deleted IObservableList<T> from the project since it's no longer used. I'll email you the code to make understanding these changes easier. I hope you'll incorporate them into the project download from this blog.
I continued digging deeper and finally created a solution that includes three types (bag, list, and set) of observable collections. See my blog entry for further details and to download the solution.
Thank you for sharing this.
Could you help me with this?
Is you example extendable to use events to do updates from one instance to another.
I tried running the same program twice. But the second instance didn't get updated.
That is really nice!
Thanks for the code example
Wow, danke! Es hat mir sehr geholfen :)
nice post
Post a Comment