Sony Arouje

a programmer's log

Archive for November 7th, 2012

NHibernate and Oracle Long Varchar field

with 3 comments

I am working on a project to create a webservice on top of a Legacy Oracle db. As usual I used NHibernate as the ORM. Every thing is working as expected until I hit a table with a field type of ‘Long’. NHibernate freezes whenever it try to insert records to this table. NHibernate logging was enabled and I could see no action is taking place after issuing the Insert command to this table. So to pin point the issue I commented out the filed with LONG type from the hbm mapping. Then I repeated the test, this time insertion went well without any freezing.

I confirmed that the issue is with this particular Oracle Long datatype field. I guessed that parameter type NHibernate set for this filed is the culprit. I explicitly set the type in hbm file and tried many types supported by NHibernate, but in vain. I wasted so much time trying different types in hbm and googling for a solution, at last I decided to come up with a custom type to solve this issue. I got a starting point from a blog post of nforge.

Let’s see the Custom type I created for Oracle Long.

using System;
using System.Data;
using NHibernate.UserTypes;
using NHibernate;
using Oracle.DataAccess.Client;
namespace Net.Infrastructure.Persistence
{
    public class OracleLongVarcharType:IUserType
    {
        private static readonly NHibernate.SqlTypes.SqlType[] SQL_TYPES =
            { NHibernate.NHibernateUtil.StringClob.SqlType };

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

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

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

        public bool Equals(object x, object y)
        {
            if ( object.ReferenceEquals(x,y) ) return true;
            if (x == null || y == null) return false;
            return x.Equals(y);
        }

        public int GetHashCode(object x)
        {
            if (x == null)
                return 0;

            return x.GetHashCode();
        }

        public bool IsMutable
        {
            get { return false; }
        }

        public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
        {
            object obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
            if (obj == null) return null;
            string val = Convert.ToString(obj);
            return val;
        }

        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            if (value == null)
                ((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
            else
            {
                OracleCommand oraCmd = cmd as OracleCommand;
                if (oraCmd != null)
                {
                    oraCmd.Parameters[index].OracleDbType = Oracle.DataAccess.Client.OracleDbType.Long;
                    oraCmd.Parameters[index].Value = Convert.ToString(value);
                }
                else
                {
                    ((IDataParameter)cmd.Parameters[index]).Value = Convert.ToString(value);
                }

            }
        }

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

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

        public NHibernate.SqlTypes.SqlType[] SqlTypes
        {
            get { return SQL_TYPES; }
        }
    }
}

I don’t think the implementation need much explanation. Pay attention to NullSafeGet and NullSafeSet. In NullSafeSet I cast the IDBCommand to Oracle command, then reassign the parameter type as OracleDbType.Long. That’s it, we created a new custom type to handle Oracle Long type.

Let’s see how to add this new type to the mapping in hbm file.

<property name="TextData" column="TEXT_DATA" not-null="false" access="property" 
type="Net.Infrastructure.Persistence.OracleLongVarcharType,Net.Infrastructure.Persistence"/>

The Insert command NHibernate create before adding the custom type is as shown below

INSERT INTO TEXT_STORAGE (TEXT_DATA, CHANGE_DATE, CHANGE_USER, TEXT_NUMBER) VALUES (:p0, :p1, :p2, :p3);:p0 = ‘Purchase order text’ [Type: String], :p1 = 1352287328 [Type: Int64 (0)], :p2 = ‘sarouje’ [Type: String (0)], :p3 = 100588726 [Type: Int64 (0)]

after adding the Custom type

INSERT INTO TEXT_STORAGE (TEXT_DATA, CHANGE_DATE, CHANGE_USER, TEXT_NUMBER) VALUES (:p0, :p1, :p2, :p3);:p0 = ‘Purchase order text’ [Type: String (0)], :p1 = 1352287328 [Type: Int64 (0)], :p2 = ‘sarouje’ [Type: String (0)], :p3 = 100588726 [Type: Int64 (0)]

Update

Once I did some testing I realize that NHibernate is not fetching data from the table with Long type. After some search I came to know that, while creating the Oracle Command we should set InitialLONGFetchSize to a non zero value. By default OracleCommand set InitialLONGFetchSize to zero. If the InitialLONGFetchSize is zero then data retrieval is deferred until that data is explicitly requested by the application. Even I request explicitly nothing is returning. So only option left is set  InitialLONGFetchSize to a non zero. Then the question is how will I get the Command to set this property. I researched for Listeners, Interceptors, etc, nothing helped me. So I decided to inherit from OracleDataClientDriver and create my own as shown below.

using NHibernate.Driver;
using Oracle.DataAccess.Client;
namespace Net.Infrastructure.Persistence
{
    public class OracleDriverExtended:OracleDataClientDriver 
    {
        public override void AdjustCommand(System.Data.IDbCommand command)
        {
            OracleCommand cmd = command as OracleCommand;
            if (cmd != null)
                cmd.InitialLONGFetchSize = -1;
        }
    }
}

Now we have to instruct NHibernate to use the new driver I created, update the driver info in hibernate.cfg.xml as shown below.

<property name="connection.driver_class">Net.Infrastructure.Persistence.OracleDriverExtended,
Net.Infrastructure.Persistence</property>

I reran the test and now I could see the data from the Long field. Relieved….

Hope this post will help some one. Happy coding.

Written by Sony Arouje

November 7, 2012 at 5:42 pm

%d bloggers like this: