Thursday, December 6, 2007

Collections for WPF and NHibernate

My current programming project is on the Microsoft .NET platform, and I'm writing code in the C# language. I'm using NHibernate — a popular object/relational mapping tool — in the data access layer, and Windows Presentation Foundation (WPF) for the user interface. One of the features making WPF so attractive is data binding. Although I was initially disappointed when I found out that NHibernate's collections don't work correctly out-of-the-box with WPF data binding, I soon found Gary DeReese's blog entry which provides a (partial) solution. Gary's "NotifyingCollectionDemo" shows how a custom collection type can be built to rectify NHibernate's shortcoming. After studying how his ObservableList (which is actually an observable bag) was created relative to the NHibernate API, I began work on my own "ObservableCollections" project which includes three custom collection types: observable bag, observable list, and observable set.

I've finished creating these custom collection types, so you can download the result now (updated 2008-Dec-10) to use in your own projects. The zip archive contains a Visual Studio 2005 solution that includes three projects. The "ObservableCollections" project is the library containing the custom collections. The "ObservableCollections Demo" project is a WPF program that you can run to play with the contents of a sample set and sample list collection. And the "CoreHelpers" project contains some miscellaneous helper classes that I use in various projects. Before running the demo project, however, you'll need to do the following:
  • Add references to the library and demo projects, pointing to the following NHibernate assemblies on your computer (unless already in the GAC): NHibernate, Castle.DynamicProxy, Iesi.Collections, and log4net.

  • Change the absolute path of the database in the hibernate.cfg.xml file to match its location on your computer.
The "CoreHelpers" project includes a OneToManyAssocSync helper class that makes it easy to maintain in-memory the semantics of a one-to-many bidirectional association. The demo project shows how to use it. There is also a ManyToManyAssocSync class for easily maintaining many-to-many bidirectional associations. (See this blog entry for a note about the renaming of these classes from their former names.)

18 comments:

Unknown said...

Really thanks for your good job !

Nadav said...

I am interesting in integrating your ObservableCollections into a VS2005 WinForm project.

Since I cannot open your WPF example, will it be possible for you to describe the steps required to integrate your classes into a WinForm project?

Thanks for sharing!

Adrian Alexander Pinter said...

Using my collections in the absence of WPF hadn't even occurred to me. Thinking about it now, though, nearly every NHibernate project has bidirectional associations, which might be your reason. You shouldn't have any problem doing so. My "Example" project is in WPF, but the "Library" project doesn't reference any WPF assembly. Just copy the code files from there into your solution, then you can start with your observable fun.

Anonymous said...

Tanx for your effort. I tried to use this with NHibernate 2.0 beta2 and there seems to be some uppgrades which breakes your code. Are you planning to make some updates. ( Sorry to say, I'm not so skilled on NHibenate that I "dare" to fix the problems.. I'm just a NHibernate user, with problems of my own :)

Adrian Alexander Pinter said...

"just a NHibernate user" is how we all start out, and probably wish to remain, but it often doesn't work out that way (didn't for me). I don't plan to use NHibernate 2.0 until after it's out of beta, so don't expect any 2.0-related updates from me before then (I do, however, upload other improvements every now and then). I encourage you to start digging into NH's guts (it's addictive, trust me), then you may be able to fix some of those problems yourself (let me know if you do).

Unknown said...

Hi master !

Man, NHibernate now is RC1, and in newsgroup they say no changes will come for official release.

Anonymous said...

Do you can create a wiki reference about your collections work and how use it in the users-wiki of NHForge ?
http://www.nhforge.org/groups/nhusers/wiki/default.aspx

To create it is enough sign in.
If you don't know NHForge it is the NHibernate community site.
You are welcome.

WaYdotNET said...

Hi, now NH is 2.0 stable version... u have a possiblity to update ur code? :D

ps.

sorry 4 bad english

Adrian Alexander Pinter said...

I finally installed NH 2.0... sorry for the delay. I already got my library/demo working. It was incredibly simple! In the ObservableBagType, ObservableListType, and ObservableSetType classes I just had to change the signature of 'object Instantiate()' to 'object Instantiate(int anticipatedSize)'. You guys had me worried it was going to be something big.. jajaja.

David Veeneman said...

Thanks for the library! I'm just getting started on WPF, and it looks like just what I need.

Does the current version of the library work with NHibernate 1.2? I assume it doesn't, since it is built against NHibernate 2.0 references. If that's the case, is there a way to get the NHibernate 1.2 version of the library? Thanks again.

David Veeneman
Foresight Systems

David Veeneman said...

Answered my own question, after reading your last comment. Backed out the 'int anticipatedSize' parameters, compiled against NHibernate 1.2 references, and all is well.

Thanks again for the library!

David Veeneman
Foresight Systems

Adrian Alexander Pinter said...

I've upgraded the solution to reference NH 2.1 (the current trunk). Because of changes made to NH's persistent-collection base classes, I needed to take a new approach for intercepting collection-change events. Over on the nhibernate-development Google Group, Andrew Cullen provided a nice alternative. I haven't tested with previous NH versions, although I think it'll work.

mahara said...

I just recently tried using it on NHibernate 2.1 Beta 1, but it seems there's a problem with adding item into the ObservableSet`1. I'm using it in WPF.

Thanks for this!

Adrian Alexander Pinter said...

Hello Mahara, thanks for your feedback. Unfortunately, I don't have time now to test out this latest NH release myself. Please investigate the matter and let me know.

mahara said...

Overall, after I compared the MS ObservableCollection`1 and your ObservableSet`1 implementations, I saw a small difference in the Add logic. It seems that WPF also requires that the added item index to be included in the collection changed notification.

So, I changed your current implementation of ObservableSet`1.Add() method from this:

[code]
OnCollectionChanged( NotifyCollectionChangedAction.Add, item );
[/code]

to this:

[code]
int index = this.IndexOf(item);
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (object) item, index));
[/code]

And finally, the problem I encountered disappeared completely.

Adrian Alexander Pinter said...

I'm glad you got your app working, Mahara. I hadn't put that code in since, in my various tests, adding items to sets didn't pose any problems. For add operations at least, WPF seems smart enough to append to the end of its list (i.e. ListView.Items, TreeView.Items, TreeViewItem.Items) if no explicit index is supplied. So I'm not sure the circumstances under which your problem arose.

mahara said...

Ah, I just knew if WPF is smart enough adding the indexes.

The problematic behavior I encountered was happening when using the Xceed WPF DataGridControl (version 3.2). Actually, it worked just fine using the previous version (3.1) which makes me confused.
And yes, I'm not using standard WPF controls as you mentioned before.

Perhaps, it is a bug within the DataGridControl itself, but the workaround I proposed works for me so far.

Michal Ciechan said...

Thanks HappyNomad for the collections, and Mahara for fixing the Add bug (needing to pass the index of the newly created item, this fixed my app randomly crashing on 2nd item being added!)