-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 
Author Message
 Post subject: Entity association with non-null values as nulls.
PostPosted: Thu Aug 09, 2007 4:27 pm 
Newbie

Joined: Tue May 01, 2007 10:20 am
Posts: 9
Hi all.

I'm running NH 1.2 GA, but can try using the trunk if there is something specifically useful.

I'm running on a legacy system (AS400) with tables made years ago and pretty set in stone. The fields cannot contain null values. Foreign Keys are given a special value to designate them as "null" (most number id's are 0, string id's are empty strings, etc).

I'm looking for a way to handle this so that when NHibernate sees one of these values, it automatically determines that it's null, and sets the object's properties as such. I found this pretty easily in the code, and can make a hack, but I'm looking for a better way.

One thought was to create a new user type, extending many-to-one and overriding ResolveIdentifier, but I'm not sure how that would work with entity types. The Many-To-One, etc. nodes in the XML doesn't seem to have a "type" argument. Nor can the TypeFactory be changed to return something else for a many-to-one type.

Right now, reading from the database is fine, because I can just ignore when there are no entities found for id 0 (although this does cause an unneeded query). It's writing back to the database where it sputters out, trying to write a null value.

Any ideas?
Thanks.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 09, 2007 5:38 pm 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
I wrote a user type to get around it. Source code is at the bottom. This is only for int32 type, but you should be able to easily adapt it for other types. You use it on the <id> element of the one-side of the relationship (rather than the <many-to-one> element). For example:

Code:
<class name="...">
  <id name="ID"
      column="id"
      type="PatientSys.Data.UserTypes.ZeroAsNullInt32IdentifierType, PatientSys.Data"
      unsaved-value="0"
  >
    <generator class="identity" />
  </id>
</class>


Code for the user type:
Code:
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;

using NHibernate;
using NHibernate.Type;
using NHibernate.SqlTypes;

namespace PatientSys.Data.UserTypes
{
   /// <summary>
   /// see http://forum.hibernate.org/viewtopic.php?t=935678&highlight
   ///     http://forums.hibernate.org/viewtopic.php?t=933785
   /// </summary>
   [Serializable]
    public class ZeroAsNullInt32IdentifierType : AbstractType, IIdentifierType
   {
      // Has to inherite from AbstractType because we need to override NullSafeSet(),
      // which is not exposed in anything "lower" than AbstractType

      private const int ZERO = 0;

      public ZeroAsNullInt32IdentifierType() { }

      #region IIdentifierType Members

      /// <summary>
      ///
      /// </summary>
      /// <param name="xml"></param>
      /// <returns></returns>
      public object StringToObject(string xml)
      {
         // This method can return null (does so in Int32Type)

         object result = FromString(xml);
         if (result != null && ZERO.Equals((int)result))
         {
            result = null;
         }
         return result;
      }

      public object FromStringValue(string xml)
      {
         // This method cannot return null

         return int.Parse(xml);
      }

      #endregion

      public override object DeepCopy(object val)
      {
         // val would be an int, so just returning it would return a copy
         return val;
      }

      public override bool Equals(object x, object y)
      {
         object xTransformed = ((x == null) || ZERO.Equals(x)) ? ZERO : x;
         object yTransformed = ((y == null) || ZERO.Equals(y)) ? ZERO : y;

         return NHibernate.Util.ObjectUtils.Equals(xTransformed, yTransformed);
      }

      /// <include file='IType.cs.xmldoc'
      ///      path='//members[@type="IType"]/member[@name="M:IType.FromString"]/*'
      /// />
      /// <remarks>
      /// <para>
      /// This implementation forwards the call to <see cref="FromStringValue"/> if the parameter
      /// value is not empty.
      /// </para>
      /// <para>
      /// It has been "sealed" because the Types inheriting from <see cref="NullableType"/>
      /// do not need and should not override this method.  All of their implementation
      /// should be in <see cref="FromStringValue"/>.
      /// </para>
      /// </remarks>
      public override sealed object FromString(string xml)
      {
         // Copied from NullableType

         return (xml == null || xml.Length == 0) ? null : FromStringValue(xml);
      }

      public override int GetColumnSpan(NHibernate.Engine.IMapping mapping)
      {
         return 1;
      }

      public override bool HasNiceEquals
      {
         get { return true; }
      }

      public override bool IsMutable
      {
         get { return false; }
      }

      public override string Name
      {
         get { return "ZeroAsNullInt32IdentifierType"; }
      }

      public override sealed object NullSafeGet(IDataReader rs, string name, NHibernate.Engine.ISessionImplementor session, object owner)
      {
         // Copied from NullableType
         return NullSafeGet(rs, name);
      }

      public override object NullSafeGet(IDataReader rs, string[] names, NHibernate.Engine.ISessionImplementor session, object owner)
      {
         // Copied from NullableType
         return NullSafeGet(rs, names[0]);
      }

      public virtual object NullSafeGet(IDataReader rs, string name)
      {
         int index = rs.GetOrdinal(name);

         if (rs.IsDBNull(index))
         {
            return null;
         }
         else
         {
            int fieldValue = Convert.ToInt32(rs[index]);
            // If it is zero in the database, interpret that as null
            return ZERO.Equals(fieldValue) ? null : (object)fieldValue;
         }
      }

        public override void NullSafeSet(IDbCommand cmd, object value, int index, bool[] settable, NHibernate.Engine.ISessionImplementor session)
        {
            NullSafeSet(cmd, value, index, session);
        }

        public override void NullSafeSet(IDbCommand cmd, object value, int index, NHibernate.Engine.ISessionImplementor session)
        {
         // TODO: is this override even needed now that we don't write a zero for null into the database?
            IDataParameter parm = cmd.Parameters[index] as IDataParameter;
            parm.Value = value == null ? DBNull.Value : value;
        }

      public override Type ReturnedClass
      {
         get { return typeof(Int32); }
      }

      public override SqlType[] SqlTypes(NHibernate.Engine.IMapping mapping)
      {
         return new SqlType[] {
                //new Int32SqlType()
                SqlTypeFactory.Int32
            };
      }

      [Obsolete("This method was renamed ToLoggableString() from NHibernate 1.0 to 1.2")]
      public string ToString(object value, NHibernate.Engine.ISessionFactoryImplementor factory)
      {
         return ToLoggableString(value, factory);
      }

      public override string ToLoggableString(object value, NHibernate.Engine.ISessionFactoryImplementor factory)
      {
         // TODO: this may cause a problem, as a null value may come in; but I don't know if it will.
         return value.ToString();
      }

      public override bool IsDirty(object old, object current, bool[] checkable, NHibernate.Engine.ISessionImplementor session)
      {
         // TODO: this overload of IsDirty is new in NHibernate 1.2.  Don't know what the "checkable"
         // parameter is, so we just call the other IsDirty for now.
         return this.IsDirty(old, current, session);
      }
   }
}

_________________
Karl Chu


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 09, 2007 5:48 pm 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
I just realize we changed our code some time ago to not write a zero to the foreign key any more. To write a zero as null into the foreign column, changed the NullSafeSet() to as below:

Code:
public override void NullSafeSet(IDbCommand cmd, object value, int index, NHibernate.Engine.ISessionImplementor session)
{
   IDataParameter parm = cmd.Parameters[index] as IDataParameter;
   parm.Value = value == null ? ZERO : value;
}

_________________
Karl Chu


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.