Data-driven NHibernate with .NET 4.0 and the DynamicEntity Class

March 28, 2011 | NHibernate

The idea for this post came by a very good friend who wanted to modify his data access layer (DAL) in order to use NHibernate.

The DAL contains a base class that generates SQL queries and the application were it's used is completely data-driven. There is no domain, no classes, and the behaviour is bound in various event handler methods. Changes to the database (columns, row data, etc) dictate how the application behaves.

The problem that comes when trying to port this kind of DAL to use NHibernate is that we have to create POCOs in order to persist everything to the database. Since the application is data-driven we would end up using an anemic domain model holding just the data to persist to the database and no behaviour at all.

The solution for the problem is to use dictionaries as entities, a little-known feature of NHibernate which allows us to define our entities as dictionaries instead of statically typed objects.

Here is how to define a mapping, (notice the entity-name instead of a class name):

The entity-name in mapping

The only thing we have is the mapping, no classes. In order to create a Currency object we create the following dictionary:

var currency = new Dictionary<string, object>()
{
    { "ISOCode","GBP" },
    { "EnglishName","United Kingdom Pound" },
    { "ExchangeRateEURtoCurrency",0.87780 },
    { "ExchangeRateUpdatedOn",DateTime.UtcNow },
    { "IsEnabled",true },
    { "Symbol",null }
};

As you can see, the above code is cumbersome. But we can do something about it.

Taking advantage of the .NET 4.0 and the DynamicObject Class, we can create a type deriving from the DynamicObject Class and specify dynamic behaviour at run time. 

Let's name our class, DynamicEntity. It must be able to:

  1. Accept a string in the .ctor specifying the entity name.
  2. Set properties (PropertyName = key, PropertyValue = value) on the internal dictionary.
  3. Get properties (similar to above) from the internal dictionary.
  4. Being able to expose the internal dictionary as property for NHibernate usage.
  5. Being able to expose it's name as property for NHibernate usage.

Here is the DynamicEntity class:

using System;
using System.Collections.Generic;
using System.Dynamic;

public sealed class DynamicEntity : DynamicObject
{
    private readonly IDictionary<string, object> dictionary
        = new Dictionary<string, object>();

    private readonly string entityName;

    public DynamicEntity(string entityName)
    {
        this.entityName = entityName;
    }

    public string Name
    {
        get
        {
            return this.entityName;
        }
    }

    public IDictionary<string, object> Map
    {
        get
        {
            return this.dictionary;
        }
    }

    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        if (!this.dictionary.TryGetValue(binder.Name, out result))
        {
            return false;
        }

        return true;
    }

    public override bool TrySetMember(
        SetMemberBinder binder, object value)
    {
        string key = binder.Name;

        if (this.dictionary.ContainsKey(key))
        {
            this.dictionary.Remove(key);
        }

        this.dictionary.Add(key, value);

        return true;
    }
}

Finally, here is an integration test in action:

[Fact]
public void NHibernateShouldBeAbleToPersistCurrency()
{
    dynamic currency = new DynamicEntity("Currency");

    currency.ISOCode                   = "GBP";
    currency.EnglishName               = "United Kingdom Pound";
    currency.ExchangeRateEURtoCurrency = 0.87780;
    currency.ExchangeRateUpdatedOn     = DateTime.UtcNow;
    currency.IsEnabled                 = true;
    currency.Symbol                    = null;

    object id;

    using (var tx = Session.BeginTransaction())
    {
        id = Session.Save(currency.Name, currency.Map);
        tx.Commit();

        Assert.NotNull(id);
    }

    Session.Clear();

    using (var tx = Session.BeginTransaction())
    {
        var loadedCurrency = Session.Load(currency.Name, id);
        tx.Commit();

        Assert.NotNull(loadedCurrency);
    }

    Session.Flush();
}

In the above test, for Session I use the ISession.GetSession(EntityMode.Map).

Download the sample code here. Updated versions will be available here.