Monday, February 1, 2010

MultiuseModel-View (MMV) Object modeling pattern with WPF and WCF: Hibernation



Download sample ObservableSet File
Download sample ObservableSetType File
Download sample ObservableCollectionType File
Download sample DatabaseObject File
Download sample DatabaseCollection File
Download sample DatabaseSet File
Download sample DatabaseCollectionEx File



In my previous article I went over how to break up a multiuse object into logical parts. In this article I will talk about how to build an object-oriented data layer using a DatabaseObject and NHibernate.



About NHibernate


NHibernate is an Object-Relational Mapping (ORM) for .NET. I've used it extensively and to be honest it's the best library for manipulating data that I've dealt with by far. There are a few different ways in which you can map a class with NHibernate: using XML files, using Attributes, or using a mapping classes with Fluent Hibernate. Personally I use Attributes since it is the least verbose choice and it is also the easiest to extend.


NHibernate is very powerful when it comes down to mapping collections, however, it is also very particular about how it handles collections internally (specifically speaking, it is particular about the lazy-loading functionality). Out of the box NHibernate lets you only use either IList<T> or ISet<T> for collections, and behind the scenes it creates an instance of a custom type (PersistantGenericBag<T>). For WPF applications using IList or ISet is inconvenient because of the lack of change notifications. Ideally we would like to use ObservableCollection instead.


While you cannot use ObservableCollection<T> by default, NHibernate allows you to define your own custom collection. And that's exactly what we should do: we have to create an ObservableSet<T> (which is a HashSet<T> collection that implements INotifyCollectionChanged) and we have to write two custom classes to play nicely with NHibernate: ObservableCollectionType and ObservableSetType.


public class ObservableSet<T> : HashedSet<T>, ISet<T>, INotifyCollectionChanged
{
 public event NotifyCollectionChangedEventHandler CollectionChanged;

 // WPF requires the list index to be passed back to
 // itself when removing an item from the collection.
 // Sets have no indices, so they are hereby added.
 public int IndexOf(T item) { return addOrder.IndexOf(item); }

 private IList<T> addOrder = new List<T>();

 public override bool Add(T item)
 {
  bool isChanged = base.Add(item);
  if (isChanged)
  {
   addOrder.Add((T)item);
   OnCollectionChanged(NotifyCollectionChangedAction.Add, item);
  }
  return isChanged;
 }

 public override bool Remove(T item)
 {
  // WPF requires the list index to be passed back to itself:
  int index = IndexOf(item);
  bool isChanged = base.Remove(item);
  if (isChanged)
  {
   addOrder.Remove((T)item);
   if (CollectionChanged != null)
    CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (object)item, index));
  }
  return isChanged;
 }

 public override void Clear()
 {
  base.Clear();
  addOrder.Clear();
  OnCollectionChanged(NotifyCollectionChangedAction.Reset, null);
 }

 /// <summary>
 /// Raises the <see cref="CollectionChanged"/> event to indicate that item(s)
 /// have been added to, or removed from, this collection.
 /// </summary>
 protected virtual void OnCollectionChanged(NotifyCollectionChangedAction action, object changedItem)
 {
  if (CollectionChanged != null)
   CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));
 }
}

internal class ObservableCollectionType<T> : IUserCollectionType
{
        public bool Contains(object collection, object entity)
        {
            return ((IList<T>)collection).Contains((T)entity);
        }

        public IEnumerable GetElements(object collection)
        {
            return (IEnumerable)collection;
        }

        public object IndexOf(object collection, object entity)
        {
            return ((IList<T>)collection).IndexOf((T)entity);
        }

        public object Instantiate(int anticipatedSize)
        {
            return new ObservableCollection<T>();
        }

        public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
        {
            return new DatabaseCollection<T>(session);
        }

        public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)
        {
            IList<T> result = (IList<T>)target;
            result.Clear();
            foreach (object item in ((IEnumerable)original))
                result.Add((T)item);
            return result;
        }

        public IPersistentCollection Wrap(ISessionImplementor session, object collection)
        {
            return new DatabaseCollection<T>(session, (ObservableCollection<T>)collection);
        }
}

internal class ObservableSetType<T> : IUserCollectionType
{
        public bool Contains(object collection, object entity)
        {
            return ((ISet<T>)collection).Contains((T)entity);
        }

        public IEnumerable GetElements(object collection)
        {
            return (IEnumerable)collection;
        }

        public object IndexOf(object collection, object entity)
        {
            return -1;
        }

        public object Instantiate(int anticipatedSize)
        {
            return new ObservableSet<T>();
        }

        public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
        {
            return new DatabaseSet<T>(session);
        }

        public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)
        {
            ISet<T> result = (ISet<T>)target;
            result.Clear();
            foreach (object item in ((IEnumerable)original))
                result.Add((T)item);
            return result;
        }

        public IPersistentCollection Wrap(ISessionImplementor session, object collection)
        {
            return new DatabaseSet<T>(session, (ObservableSet<T>)collection);
        }
}


The DatabaseObject<T> Class


When using NHibernate, creating a class that could load or save an object is extremely trivial. The implementation of a DatabaseObject<T> class would look something like this:


public abstract class DatabaseObject<T> : ObservableObject<T>
{
        public void Save()
        {
            ISession session = NHibernateHelper.Configuration.BuildSessionFactory().OpenSession();
            session.SaveOrUpdate(this);
            session.Flush();
            session.Close();
        }

        public void Load(object id)
        {
            ISession session = NHibernateHelper.Configuration.BuildSessionFactory().OpenSession();
            T loaded = (T)session.Load(ObservableObject.CreateOverridenType(typeof(T)), id);
            foreach (PropertyInfo pi in typeof(T).GetProperties())
                pi.SetValue(this, pi.GetValue(loaded, null), null);
            session.Flush();
            session.Close();
        }
}

There are a couple of things worthwhile noting about the DatabaseObject<T> class.


First of all, notice that we flush and close the session every time we load or save an object. This is important because the "lazy load" functionality in NHibernate requires a session to be open for as long as values haven't been loaded. For client-server apps this is a very bad thing: for one we don't want to maintain a session open (which implies an open connection to the database) while data is transfered from the server to the client and while the client loads the remainder of the data. And for two, depending on what type of data transfer protocol you are using, proxy objects might not be supported at all, resulting in incomplete data on the client. Therefore what we want is to transfer all the data from the server to the client at once. This is the reason why we don't support lazy-loading on this framework. However, it is altogether possible that you might be writing a 2-tier app where the client is sitting right on top of the database, and for this scenario I will show you how to modify the DatabaseObject to support lazy-loading on a future article.


Second, notice that on the first line of the Save and Load methods, there is a reference to the NHibernateHelper class. This class is the real deal for using NHibernate in conjunction with our previously written ObservableObject<T> class. The purpose of the NHibernateHelper class is simply to generate and keep track of object maps. It contains a public Configuration property so that any class that wants to create a session does so with the proper mappings.


internal sealed class NHibernateHelper
 {
        private const string CollectionTypeXmlAttribute = "collection-type";
        private const string ClassXmlAttribute = "class";
        private static readonly string[] RelationsXmlElements = { "many-to-many", "one-to-many" };

        private static readonly Dictionary<Type, Type> _MappedClasses;
        private static readonly Dictionary<string, Type> _CollectionElementsToTypes;
        private static readonly Configuration _Configuration;
        public static Configuration Configuration { get { return (_Configuration); } }

        static NHibernateHelper()
        {
            _MappedClasses = new Dictionary<Type, Type>();
            _CollectionElementsToTypes = new Dictionary<string, Type>();
            _CollectionElementsToTypes["bag"] = typeof(ObservableCollectionType<>); 
            _CollectionElementsToTypes["list"] = typeof(ObservableCollectionType<>);
            _CollectionElementsToTypes["set"] = typeof(ObservableSetType<>);
            _Configuration = new Configuration();
            _Configuration.Configure();

            // We first create overriden types for all classes that have a mapping. 
            // This is so that if there is a reference to another type (call it A) within a given type (call it B) 
            // we already have created the type A's overriden type and we can substitue B's map with the derived type.
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(typeof(ClassAttribute), false).Length > 0)
                    {
                        Type currentType = type;
                        Type overridenType = ObservableObject.CreateOverridenType(currentType);

                        _MappedClasses[currentType] = overridenType;
                    }
                }
            }

            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(typeof(ClassAttribute), false).Length > 0) // This type defines a map
                    {
                        Type currentType = type;
                        Type overridenType = _MappedClasses[currentType];

                        // create a map XML string.
                        MemoryStream stream = new MemoryStream();
                        HbmSerializer.Default.Validate = true;
                        HbmSerializer.Default.Serialize(stream, currentType);
                        stream.Position = 0;

                        string currentTypeMap = new StreamReader(stream).ReadToEnd();
                        stream.Close();


                        // Collections are tricky business with NHibernate. By default NHibernate likes to deal with IList because internally
                        // it implements it's own custom persistant collections. What we need to do here is to add the "collection-type" attribute 
                        // of the X-to-many elements to point to our custom collection which implements INotifyCollectionChanged.
                        //XmlTextReader reader = new XmlTextReader(new MemoryStream(Encoding.UTF8.GetBytes(currentTypeMap)));
                        while (reader.Read())
                        {
                            // we find an opening xml element of collection type (bag/set/list). We hope to find an X-to-many element within.
                            if (_CollectionElementsToTypes.Keys.Contains(reader.Name) && reader.NodeType == XmlNodeType.Element && reader.GetAttribute(CollectionTypeXmlAttribute) == null)
                            {
                                string elementName = reader.Name; // we store the current element name to later see if we find a matching close element.
                                int lineNumber = reader.LineNumber - 1;// LineNumber property is 1 based. we want to use 0 based arrays.

                                while (reader.Read())
                                {
                                    if (reader.Name == elementName && reader.NodeType == XmlNodeType.EndElement) // we found the closing element for the collection.
                                        break;

                                    // we found an opening relation element (X-to-many) with a "class" attribute defined 
                                    // note: the "class" attribute tells us what type the elements of the collection will be.
                                    string classNameAttribute = reader.GetAttribute(ClassXmlAttribute);
                                    if (RelationsXmlElements.Contains(reader.Name) && reader.NodeType == XmlNodeType.Element && classNameAttribute != null)
                                    {
                                        Type collectionItemType = Type.GetType(classNameAttribute);
                                        string attributeValue = _CollectionElementsToTypes[elementName].MakeGenericType(collectionItemType).ToString();
                                        attributeValue = attributeValue.Replace(collectionItemType.FullName, string.Format("{0}, {1}", collectionItemType.FullName, collectionItemType.Assembly.GetName().Name));
                                        attributeValue = attributeValue + ", " + Assembly.GetExecutingAssembly().GetName().Name;

                                        string[] lines = currentTypeMap.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.None);

                                        attributeValue = lines[lineNumber].Insert(lines[lineNumber].LastIndexOf(" "), string.Format(@" {0}=""{1}""", CollectionTypeXmlAttribute, attributeValue));

                                        currentTypeMap = currentTypeMap.Replace(lines[lineNumber], attributeValue);
                                    }
                                }
                            }
                        }

                        // because we want to offer the ObservableObject's functionality (I.E. property notifications and commands)
                        // we want to make sure that the mappings use the overriden type constructed by ObservableObject 
                        // instead of their base class counterparts.
                        foreach (KeyValuePair<Type, Type> mappedClass in _MappedClasses)
                        {
                            // NHibernate mappings sometimes allows specifying a type name without an assembly name. Therefore we
                            // want to make sure that we first replace the assembly qualified type name, and then the
                            // simple type name afterwards to avoid partial matches.
                            if (mappedClass.Key != currentType)
                            {
                                string assemblyQualifiedTypeName = string.Format(@"""{0}, {1}""", mappedClass.Key.FullName, mappedClass.Key.Assembly.GetName().Name);
                                if (currentTypeMap.Contains(assemblyQualifiedTypeName))
                                    currentTypeMap = currentTypeMap.Replace(assemblyQualifiedTypeName, string.Format(@"""{0}, {1}""", mappedClass.Value.FullName, mappedClass.Value.Assembly.GetName().Name));

                                string typeName = string.Format(@"""{0}""", mappedClass.Key.FullName);
                                if (currentTypeMap.Contains(typeName))
                                    currentTypeMap = currentTypeMap.Replace(typeName, string.Format(@"""{0}""", mappedClass.Value.FullName));
                            }
                        }

                        
                        // Create mappings for overriden type (they are exactly the same as the base type, but we just have to make sure to change the class name)
                        string className = string.Format(@"class name=""{0}, {1}""", currentType.FullName, currentType.Assembly.GetName().Name);
                        string overridenClassName = string.Format(@"class name=""{0}, {1}""", overridenType.FullName, overridenType.Assembly.GetName().Name);
                        string overridenTypeMap = currentTypeMap.Replace(className, overridenClassName);

                        // add type maps to NHibernate configuration
                        Configuration.AddInputStream(new MemoryStream(Encoding.UTF8.GetBytes(currentTypeMap)));
                        Configuration.AddInputStream(new MemoryStream(Encoding.UTF8.GetBytes(overridenTypeMap)));
                    }
                }
            }
        }
}

There are three important functons that the NHibernateHelper class must perform.
First, the NHibernateHelper class is in charge of generating mappings for the derived objects that are created by the ObservableObject class. Without these maps NHibernate would not be able to load any data onto our objects.
Second, it is in charge of replacing reference to custom types (types created by the end developer) with references to their derived types. In other words, we want to tell NHibernate to construct and load an object derived of the defined class so that we have the nice property changed and commands functionality provided by ObservableObject<T>.
Third, we also want to ensure that collections on the objects have notifications (so that they play nicely with WPF). Which brings me to the next section.



Collections


When designing types, it is quite likely that you are going to define collections just as frequently as single-object properties. Since WPF requires collections to implement INotifyCollectionChanged for binding purposes, we have to come up with a custom collection (or a set of custom collections in this case) that implements INotifyCollectionChanged and it is also able to persist itself using the NHibernate engine by inheriting PersistentGenericBag<T> or PersistentGenericSet<T>.


[Serializable]
public class DatabaseCollection<T> : PersistentGenericBag<T>, INotifyCollectionChanged 
{
 public event NotifyCollectionChangedEventHandler CollectionChanged;

 public DatabaseCollection(ISessionImplementor session) : base(session) { }

 public DatabaseCollection(ISessionImplementor session, ICollection<T> coll) : base(session, coll)
 {

  if (coll != null)
   ((INotifyCollectionChanged)coll).CollectionChanged += OnCollectionChanged;
        }

 public override void BeforeInitialize(ICollectionPersister persister, int anticipatedSize)
 {
  base.BeforeInitialize(persister, anticipatedSize);
  ((INotifyCollectionChanged)InternalBag).CollectionChanged += OnCollectionChanged;
 }

 protected void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
 {
  if (CollectionChanged != null)
   CollectionChanged(this, args);
 }
}

[Serializable]
public class DatabaseSet<T> : PersistentGenericSet<T>, INotifyCollectionChanged
{
 public event NotifyCollectionChangedEventHandler CollectionChanged;

 public DatabaseSet(ISessionImplementor session) : base(session) { }

 public DatabaseSet(ISessionImplementor session, ISet<T> coll) : base(session, coll)
 {
  if (coll != null)
   ((INotifyCollectionChanged)coll).CollectionChanged += OnCollectionChanged;
        }

 public override void BeforeInitialize(ICollectionPersister persister, int anticipatedSize)
 {
  base.BeforeInitialize(persister, anticipatedSize);
   ((INotifyCollectionChanged)gset).CollectionChanged += OnCollectionChanged;
 }

 protected void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
 {
  if (CollectionChanged != null) 
   CollectionChanged(this, args);
 }
}

When designing your types, you should use a DatabaseCollection<T> or DatabaseSet<T> as collection types, and in addition to getting collection notifications, thanks to the magic happening in NHibernateHelper you will automatically get items that implement INotifyPropertyChanged and have ICommands for all their methods.



DatabaseCollection Extensions


It wouldn't be object-oriented design if you weren't able to call a Load or a Save method straight from a collection and get the expected functionality from it. Thanks to the nice Extension Methods feature of the .Net framework 3.5 we can do just that not only for the DatabaseCollection<T> and DatabaseSet<T> but also for any collection type that implements ICollection<T>.


public static class DatabaseCollectionEx
    {
        public static void Save<T>(this ICollection<T> collection)
        {
            ISession session = NHibernateHelper.Configuration.BuildSessionFactory().OpenSession();
            foreach (var o in collection)
                session.SaveOrUpdate(o);
            session.Flush();
            session.Close();
        }

        public static void Load<T>(this ICollection<T> collection)
        {
            ISession session = NHibernateHelper.Configuration.BuildSessionFactory().OpenSession();
            foreach (T item in session.CreateCriteria(ObservableObject<T>.CreateOverridenType()).List())
                collection.Add(item);
            session.Flush();
            session.Close();
        }

        public static void Load<T>(this ICollection<T> collection, params ICriterion[] criteria)
        {
            ISession session = NHibernateHelper.Configuration.BuildSessionFactory().OpenSession();
            ICriteria c = session.CreateCriteria(ObservableObject<T>.CreateOverridenType());
            foreach (ICriterion criterion in criteria)
                c = c.Add(criterion);
            foreach (T item in c.List<T>())
                collection.Add(item);
            session.Flush();
            session.Close();
        }
    }

As you can see, besides saving content of a collection you can also load the contents of an entire table or a reduced subset by passing criteria to the Load method.


Here is a couple of sample business objects:


[Class(Name = "AndresLook.Address, AndresLook", Lazy = false, Table = "Address")]
    public abstract class Address : DatabaseObject<address>
{
        [Id(0, Name = "AddressId", Column = "AddressId")]
        [Generator(1, Class = "identity")]
        public virtual int AddressId { get; set; }
        [Property(Column = "ContactId")]
        public virtual int ContactId { get; set; }
        [Property(Column = "Address1")]
        public virtual string Address1 { get; set; }
    }

[Class(Name="AndresLook.Contact, AndresLook", Lazy=false, Table="Contact")]
    public abstract class Contact : DatabaseObject<contact>
    {
        [Id(0, Name="ContactId", Column="ContactId")]
        [Generator(1, Class = "identity")]
        public virtual int ContactId { get; set; }

        [Property(Column="FirstName")]
        public virtual string FirstName { get; set; }

        [Property(Column="LastName")]
        public virtual string LastName { get; set; }

        [Bag(0, Name = "Addresses", Table = "Address", Lazy = CollectionLazy.False, Cascade = "save-update")]
        [Key(1, Column = "ContactId")]
        [Index(2, Column="AddressId")]
        [OneToMany(3, Class = "AndresLook.Address, AndresLook")]
        public virtual IList<address>
Addresses { get; set; }

        public Contact()
        {
            // We define "Addresses" as an observablecollection by default
            // if we instantiate Contact using the .New() method we should be able
            // to add entries to Addresses and using the .Save() method should
            // save those entries to the database as expected.
            Addresses = new ObservableCollection<address>();
        }
    }

And here is how you can tap onto the databse functionality:


Contact c = Contact.New();
c.Load(1);

List<address>
a = new List<address>();
a.Load(NHibernate.Criterion.Expression.Eq("AddressId", 1));

In my next article, I'll demonstrate how to create a CommunicationObject that transfer data from a server to a client and vice versa.