Location > Blog

Custom Domain using nHibernate dynamic-component

I have been working on providing a user defined domain object to my C# ASP.NET nHibernate project. I have been undecided on which approach to take. One difficulty is that the the mapping files I am using are embedded so they cannot be changed. After some research I had been considering two options.
  1. Using a one-to-many look up table which contained all the custom attributes.
  2. Using a dynamic-component mapping.
The first option would be straightforward to implement good for storing basic values but more difficult to query if for example you wanted to query a date range of the custom fields.

Using a dynamic-component you can add mappings to new fields in a table and query then in your HQL as if they are a column in the table. This wasn't so obvious as the documentation is somewhat vague and there aren't a lot of example available. I found a useful article on Using Hibernate to support Custom Domain Objects which used the dynamic-component mapping using Hiberante with Java. Very little has to change to use this with C# in ASP.NET. In the example the mapping files are updated as I'm using embedded mappings I would need another solution.

I decided to try using a dynamic-component as it appears to tick all the boxes. I have simplified the mapping and domain object for this example.

The Mapping File

xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
  <class name="Account" table="Account"
    <id name="Id" column="Id" type="Int32" unsaved-value="0"
      <generator class="native"/> 
    id> 
    <property column="Name" type="String" name="Name" not-null="true" length="50" /> 
    <dynamic-component name="Attributes"/> 
  class> 
hibernate-mapping> 

The Domain Object

    public class Account 
    { 
        public Account() 
        { 
            Name = string.Empty; 
            Attributes = new Hashtable(); 
        } 
 
        public virtual int Id { getset; } 
 
        public virtual string Name { getset; } 
 
        public virtual IDictionary Attributes { getset; } 
    } 
 

Storing the Mapping Configuration

The next problem I have is where should I store my configuration of the dynamic-component? Editing the mapping file is out because it is embedding. I'm working with a website so the next best thing I think is storing the configuration of the custom properties in the web.config file. I decided to implement a custom configuration section.

Configuration

When you configuring your mapping it must be done before you configured before you call BuildSessionFactory in your configuration code and after your user adds a custom field before you must refresh your configuration and reassign a new SessionFactory or you will run into problems.

     var configuration = new NHibernate.Cfg.Configuration(); 
     configuration.Configure(GetConfigFile()); 
 
     AddMappingExtensions(); 
 
     var sessionFactory = _configuration.BuildSessionFactory(); 
     var session = _sessionFactory.OpenSession(); 

Adding the Mapping

This next step really depends on where you store your mapping configuration I have made some configuration sections in the web.config alternatively you could store the configuration in a xml file somewhere.

 
        private static void AddMappingExtensions() 
        { 
            //get the mappings from the web.config you will need to implement a custom configuration section to do this. 
            foreach (ClassMappingElement mapping in WebConfiguration.NHibernate.Mappings) 
            { 
                var persistentClass = _configuration.GetClassMapping(ReflectUtil.ClassForName(mapping.Type)); 
                if (persistentClass != null
                { 
                    var componentProperty = persistentClass.GetProperty(mapping.DynamicComponent); 
                    if (componentProperty != null
                    { 
                        var component = (Component)componentProperty.Value; 
                        foreach (PropertyElement property in mapping.MappingProperties) 
                        { 
                            component.AddProperty(CreateDynamicProperty(property, persistentClass)); 
                        } 
                    } 
                } 
            } 
        } 
 
        private static Property CreateDynamicProperty(PropertyElement propertyElement, PersistentClass persistentClass) 
        { 
 
            // Create a simple value that contains the column and will be set to the property as information. 
            SimpleValue simpleValue = new SimpleValue(persistentClass.Table); 
            simpleValue.TypeName = propertyElement.Type; 
 
            // Create a new db column specification. 
            Column column = new Column(Configuration.NamingStrategy.ColumnName(propertyElement.Column)); 
            column.Value = simpleValue; 
            if (propertyElement.Length > 0) 
            { 
                column.Length = propertyElement.Length; 
            } 
            column.IsNullable = !propertyElement.NotNull; 
            if (propertyElement.Scale > 0) 
            { 
                column.Scale = propertyElement.Scale; 
            } 
            if (propertyElement.Precision > 0) 
            { 
                column.Precision = propertyElement.Precision; 
            } 
            if (!string.IsNullOrEmpty(propertyElement.Default)) 
            { 
                column.DefaultValue = propertyElement.Default; 
            } 
 
            if (persistentClass.Table != null
            { 
                persistentClass.Table.AddColumn(column); 
            } 
            simpleValue.AddColumn(column); 
 
            // Create a new property. 
            return new Property { Name = propertyElement.Name, Value = simpleValue }; 
        } 
 
 

Updating the Database Schema

Adding new fields to your database schema is very easy with nHibernate. I had over looked this feature until recently. All you need to do is create an instance of the SchemaUpdate class and update your schema. nHibernate generates the necessary SQL to add the new columns to your table.
 
var schemaUpdate = new SchemaUpdate(Configuration); 
schemaUpdate.Execute(falsetrue); 

Conclusion

I think this is a good start to providing a dynamic domain using nHibernate without stepping outside of nHibernate to achieve it. It certainly was a lot more difficult to achieve that I would have though from the out set.

There are some limitations which still need to be addressed.

  • nHibernate does not remove columns from tables when they are not in your mapping file. For example in your interface a user deletes a custom field you then call SchemaUpdate to update your database schema. This behavior is by design because in some setups your mapping file could be mapped to part of the table.
  • SchemaUpdate does not change the type or length of a field. For example if you were to change the length of a text field in your mapping the change is not reflected when you call SchemaUpdate. Again I expect this is by design.
Share this post