NHibernate ICompositeUserType split string columns

654 views Asked by At

I would implement an ICompositeUserType for a string property, that is splitted in 4 columns of 2000 chars (in a legacy oracle database, to avoid CLOB/LONG fields). Implementing the interface seems to be a good idea, but I can't understand the SetPropertyValue method. It doesn't accepts typed or string values. And I need it as mutable, so I need to implement this method.

I could have a non persistent property that encapsulates the split and join to x persitent properties, but I want something more transparent.

I've found similar questions but none responses as I expected.

-- UPDATE--

I upload some code to add context to the question:

DATABASE:

...
Value VARCHAR2(2000 BYTE)
Value2 VARCHAR2(2000 BYTE)
Value3 VARCHAR2(2000 BYTE)
Value4 VARCHAR2(2000 BYTE)
Value5 VARCHAR2(2000 BYTE)
...

HBM:

<property name="Value" type="MyAssembly.NHibernateUtils.Types.MultiColumnJoinedUserType, MyAssembly">
  <column name="DFM_Value"/>
  <column name="DFM_Value2"/>
  <column name="DFM_Value3"/>
  <column name="DFM_Value4"/>
  <column name="DFM_Value5"/>
</property>    

CLASS:

    public virtual string Value { get; set; }

MultiColumnJoinedUserType:

    /// <summary>
    /// Set the value of a property
    /// </summary>
    /// <param name="component">an instance of class mapped by this "type"</param>
    /// <param name="property"></param>
    /// <param name="value">the value to set</param>
    public void SetPropertyValue(object component, int property, object value) { ... }

In my class the component field of the method is a string, so it's impossible to change its value without changing the reference, because string is passed as value not as reference as I need.

3

There are 3 answers

0
Marc On BEST ANSWER

I spend some hours in this issue and various solutions tried:

  • Use a string[] instead of a string in a ICompositeUserType
  • Use a IUserType instead of a ICompositeUserType

Both worked in Oracle, but in SQL (I need the compatibility) the null values are set as default insted of null.

NHibernate trace in log4net:

INSERT INTO VALUETABLE (HDF_Value1, HDF_Value2, HDF_Value3, HDF_Value4, HDF_Value5) VALUES (@p1, @p2, @p3, @p4, @p5); select SCOPE_IDENTITY(); @p1 = NULL [Type: String (4000)], @p2 = NULL [Type: String (4000)], @p3 = NULL [Type: String (4000)], @p4 = NULL [Type: String (4000)], @p5 = NULL [Type: String (4000)]

But in SQLProfiler:

exec sp_executesql N'INSERT INTO VALUETABLE (HDF_Value1, HDF_Value2, HDF_Value3, HDF_Value4, HDF_Value5) VALUES (@p1, @p2, @p3, @p4, @p5); select SCOPE_IDENTITY()',N'@p1 nvarchar(4000),@p2 nvarchar(4000),@p3 nvarchar(4000),@p4 nvarchar(4000),@p5 nvarchar(4000)',@p1=default,@p2=default,@p3=default,@p4=default,@p5=default

I've tried various pieces of code on nullsafeset (use DBNull, string.NullSafeSet, ...) but nothing works for me. I can't waste more time and finally I've implemented the simplest solution, 5 "hidden" properties on mapped object and a "virtual" property that splits and joins the values, it isn't very transparent but it works as expected:

    public virtual string _Value1 { get; set; }
    public virtual string _Value2 { get; set; }
    public virtual string _Value3 { get; set; }
    public virtual string _Value4 { get; set; }
    public virtual string _Value5 { get; set; }

    public virtual string Value
    {
        get
        {
            // JOIN VALUES FROM 5 _Value properties
        }
        set
        {
            // SPLIT VALUE INTO 5 _Value properties
        }
    }

That's enough for me in this moment.

1
Stefan Steinegger On

The string is not mutable. You can safely ignore SetPropertyValue. I've implemented GetPropertyValue in my immutable user types, I think it gets called.

    public bool IsMutable
    {
        get { return false; }
    }

    public void SetPropertyValue(object component, int property, object value)
    {
      throw new NotSupportedException("Something goes wrong here");
    }


    public object GetPropertyValue(object component, int property)
    {
      var stringComponent = (string)component;
      maxLengthToRead = Math.Min(2000, stringComponent.Length - property * 2000);
      if (maxLengthToRead <= 0) return string.Empty;

      return stringComponent.Substring(property * 2000, maxLengthToRead);
    }
9
Oskar Berggren On

The ICompositeUserType is typically for when you have multiple properties in .Net (e.g. a "component"). There is an example for a Money class here: http://geekswithblogs.net/opiesblog/archive/2006/08/05/87218.aspx

Since you have just a single simple value in .Net, you should implement IUserType instead, which will still allow you to use multiple columns in the database.