Search code examples
fluent-nhibernatedictionarymappingpreferencesusertype

How to map IDictionary<string, object> in Fluent NHibernate?


I am looking to persist user preferences into a collection of name value pairs, where the value may be an int, bool, or string.

There are a few ways to skin this cat, but the most convenient method I can think of is something like this:

public class User
{
    public virtual IDictionary<string, object> Preferences { get; set; }
}

with its usage as:

user.Preferences["preference1"] = "some value";
user.Preferences["preference2"] = 10;
user.Preferences["preference3"] = true;

var pref = (int)user.Preferences["preference2"];

I'm not sure how to map this in Fluent NHibernate, though I do think it is possible.

Generally, you would map a simpler Dictionary<string, string> as:

HasMany(x => x.Preferences)
    .Table("Preferences")
    .AsMap("preferenceName")
    .Element("preferenceValue");

But with a type of 'object', NHibernate doesn't know how to deal with it. I imagine a custom UserType could be created that breaks an 'object' down to a string representing its Type and a string representing the value. We would have a table that looks kind of like this:

Table Preferences
    userId (int)
    preferenceName (varchar)
    preferenceValue (varchar)
    preferenceValueType (varchar)

and the hibernate mapping would like this:

<map name="Preferences" table="Preferences"> 
  <key column="userId"></key> 
  <index column="preferenceName" type="String" />
  <element type="ObjectAsStringUserType, Assembly">
    <column name="preferenceValue" /> 
    <column name="preferenceValueType"/> 
  </element> 
</map> 

I'm not sure how you would map this in Fluent NHibernate.

Maybe there's a better way to do this, or maybe I should just suck it up and use IDictionary<string, string>. Any ideas?


Solution

  • i would say IDictionary<string,string> would be a lot easier. However here's the code

            HasMany(u => u.Preferences)
                .Table("Preferences")
                .AsMap("preferenceName")
                .Element("preferenceType", e => e.Column("preferenceValue").Type<ObjAsStringUserType>());
    
    class ObjAsStringUserType : ImmutableUserType
    {
        public override object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            var type = (string)NHibernateUtil.String.NullSafeGet(rs, names[0]);
            var value = (string)NHibernateUtil.String.NullSafeGet(rs, names[1]);
    
            switch (type)
            {
                case "boolean":
                    return bool.Parse(value);
                    ...
                default:
                    return null;
                    break;
            }
        }
    
        public override void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            var type = value.GetType().Name;
            var valuestring = value.ToString(CultureInfo.InvariantCulture);
    
            NHibernateUtil.String.NullSafeSet(cmd, type, index);
            NHibernateUtil.String.NullSafeSet(cmd, valuestring, index + 1);
        }
    
        public override Type ReturnedType
        {
            get { return typeof(object); }
        }
    
        public override SqlType[] SqlTypes
        {
            get { return new []
            {
                SqlTypeFactory.GetString(length: 255),  // preferenceType
                SqlTypeFactory.GetString(length: 255),  // preferenceValue
            };
            }
        }
    }