views:

146

answers:

1

Hi, this was posted on hibernate.org forums and nhusers list without much luck, so I thought I would try here.

Put simply, suppose I have a class:

class A
{
   public virtual object SomeValue { get; set; }
}

the type of SomeValue is basically in the set of .NET IConvertible types (primitives like bool, byte, char, int16, double, float etc.), plus byte[] and string.

I am trying to create a nhibernate mapping for A to reflect this - so that I can basically set SomeValue to an arbitrary object (of one of the types above) and retrieve it later on on. My applogic will then reflect on it to find the type and behave accordingly.

So far I have tried Creating an implementation of IUserType to try and handle this. However I don't know what to return for the SqlType[] SqlTypes. I considered new SqlType(DbType.Object) but when I try to generate a schema from this I get a System.ArgumentException: Dialect does not support DbType.Object

If I try another data type then I get various cast exceptions when trying to convert the type. For instance if i use a DbType.Binary, and set someValue to an int32, upon attempting to commit I get System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Byte[]'.

Is there a way to achieve this?

Attached code below for a non-working implementation of IUserType (based on http://intellect.dk/post/Implementing-custom-types-in-nHibernate.aspx )

public class DataElementType : IUserType

    {
        SqlType baseType = new SqlType(DbType.Binary);
        public SqlType[] SqlTypes
        {
            get
            {
                return new[] { baseType };
            }
        }

        public System.Type ReturnedType
        {
            get { return typeof(object); }
        }

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

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

        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            return rs[names[0]];
        }

        public void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            var param = new SQLiteParameter(baseType.DbType, value);
            cmd.Parameters.Insert(index, param);
        }

        public object DeepCopy(object value)
        {
            if (value == null) return null;
            return 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;
        }
    }
A: 

It turns out to get around the problem with SqlType(DbType.Object) being unsupported by the Dialect, we make it supported by subclassing the SQLiteDialect with explicit support:

public class SQLiteDialectWithManifestTyping : SQLiteDialect
{
 public SQLiteDialectWithManifestTyping() : base()
 {
  base.RegisterColumnType(DbType.Object, "NONE");
 }
}

To use this dialect in Fluent, call Dialect() on your SQLiteConfiguration object. In NHibernate, set the configuration property dialect appropriatelly (see section 3.5.1 of the ref manual).

Then we can apply the above DataElementType implementation for our mappings (need to change the SqlTypes definition to this:

    public SqlType[] SqlTypes
    {
        get
        {
            return new[] { new SqlType(DbType.Object) };
        }
    }

Notes:

  1. It is not perfect. There is a tendency to upcast all discrete numbers to Int64 and floats to double.

  2. There is no implicit way to store large unsigned values (e.g. values of ulong >= long.MaxValue) but this is a general sqlite problem (and possible a general ado.net problem?).

  3. Due to the loss of compile time checking it is probably desireable to put some runtime checks in the NullSafeSet method to ensure the value is a primitive type. Attempting to store general objects seems to just cause the objects ToString() method to be called.

fostandy