vineri, 25 mai 2012

NHibernate List to CSV-like column mapping

List<string> to CSV (and vice-versa) type mapping using NHibernate

Experimenting with new stuff is often fun, except when it's even more fun...

For example, today I was trying out NHibernate as I've heared a few times how more mature ORM is in the .NET space comapred to Entity Framework in many ways (you can have global filters defined for records, different cascades, 2nd level caching, ect...).

So, I fired up Visual Studio, downloaded FluentNHibernate using NuGet and wanted to try out to map a list of strings to a CSV-like (varchar) column. Searching the web for this information didn't give me the results I wanted, so I started to dig a bit more.
Since this is my first try-out of NHibernate, I had some issues getting it configured properly building an ISessionFactory to my SQL Server database. I kept getting exceptions that didn't have an unambiguous meaning/message and had problems finding the root problems - how I hate this. Anyway, after a while it all worked out fine and having in mind that it's my first interaction with it, it was OK, I was expecting this.

Continuing my mapping, I built the simplest persistent data model: Employee having some tags stored in a collection and it goes like this:


public class Employee
{
        public Employee()
        {
            Tags = new List<string>();
        }
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual List<string> Tags { get; set; }
}


In my database, the Employees table has a simple nvarchar column named 'Tags' that I map to. With the help of FluentNHibernate, I built my ClassMap<Employee>:

public class EmployeeMap : ClassMap<Employee>
    {
        public EmployeeMap()
        {
            Table("Employees");
            Id(x => x.Id);
            Map(x => x.Name);
            Map(x => x.Tags).CustomType<StringListType>();
        }
    }


The interesting thing happens in StringListType class that implements NHibernate.UserTypes.IUserType.
From what I've read, to be able to do this mapping is to have a class that implements IUserType. I was a bit scared when I saw the number of methods that I need to implement, but here's what I came up with:

public class StringListType : IUserType
    {
        public SqlType[] SqlTypes
        {
            get
            {
                SqlType[] types = new SqlType[1];
                types[0] = new SqlType(DbType.String);
                return types;
            }
        }

        public Type ReturnedType
        {
            get { return typeof(IList<string>); }
        }


        public new bool Equals(object x, object y)
        {
            if (x == null)
            {
                return false;
            }

            return x.Equals(y);
        }


        public int GetHashCode(object x)
        {
            return x.GetHashCode();
        }


        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            string uriString = (string)NHibernateUtil.String.NullSafeGet(rs, names[0]);

            if (string.IsNullOrWhiteSpace(uriString))
                return new List<string>();

            return uriString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
        }


        public void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            if (value == null)
            {
                NHibernateUtil.String.NullSafeSet(cmd, null, index);
                return;
            }

            NHibernateUtil.String.NullSafeSet(cmd, value, index);
        }


        public object DeepCopy(object value)
        {
            if (value == null) return null;

            return string.Join(",", (IList<string>)value);
        }


        public bool IsMutable
        {
            get { return false; }
        }


        public object Replace(object original, object target, object owner)
        {
            return original;
        }


        public object Assemble(object cached, object owner)
        {
            return cached;
        }


        public object Disassemble(object value)
        {
            return value;
        }
    }


This is quite simple because it just involves transforming from and to a list<string> using some basic operations. As you can see, most of the stuff happens in NullSafeGet and DeepCopy.

I don't know if this is the most efficient way to do this and honestly I haven't given too much thought, but this solved my problem in a quite decent way. If you find a better way, let me know.