-->
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.  [ 5 posts ] 
Author Message
 Post subject: Legacy data with special "null" value (e.g. 0)
PostPosted: Wed Jun 14, 2006 12:16 am 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
I need to map a legacy database with two tables PARENT and CHILD (ficticious). In the database, if CHILD.ParentID has a value of zero (0), it means that it has no parent. In my C# code, when I load a child with a ParentID of 0 (i.e. no parent), NHibernate thinks it has a parent whose primary key is 0, which of course does not exist in the database and throws an error.

Is there a mapping definition that allows me specify that zero, in this case, should be treated as "null"?

Thanks in advance.

Karl

Hibernate version: NHibernate 1.0.2

Mapping documents:

Code between sessionFactory.openSession() and session.close():

Full stack trace of any exception that occurs:

Name and version of the database you are using: MS SQL2000

The generated SQL (show_sql=true):

Debug level Hibernate log excerpt:


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jun 14, 2006 3:04 pm 
Beginner
Beginner

Joined: Wed Oct 05, 2005 5:35 am
Posts: 47
Location: France
Quote:
Is there a mapping definition that allows me specify that zero, in this case, should be treated as "null"?

Don't think so...

You might want to take a look at http://forum.hibernate.org/viewtopic.php?t=935678&highlight - it's Hibernate, but applies here too.

The pb is two-fold:
1. when you read the data NH "hydrates" the object it does "if ( id != null)" check. Then it goes "session.ScheduleBatchLoad( AssociatedClass, id )". In your case you do not want it to hit the BatchLoad when id=0. Don't know what is the best startegy to handle this - but if you are stuck you could just patch NHibernate\Type\ManyToOneType.cs and such.

2. when you delete relationships you have to make sure NH inserts 0 instead of NULL for the FK you remove. That's a tricky one too. You have to make your own UserType and use it in mappings of the Id's for those entities that are used in such relationships (like Parent in your case - if you might be removing Parent from Child). The trick is to override NullSafeSet to use 0 instead of null...

Hope it helps


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jun 14, 2006 3:22 pm 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
Thanks Balagan, I had a look at the link you sent and it seems to answer my question. I have yet to try it though. I will report back here.

Thanks,
Karl


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jun 14, 2006 5:38 pm 
Senior
Senior

Joined: Wed Jun 15, 2005 4:17 am
Posts: 156
my 2 penny toughts: instead of hacking nhibernate you could try hacking the database: create some views to "normalize" the relationships and map your objects on the views. I think you're using SQL Server so you can use instead triggers to update the views.

cheers,
radu


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jun 27, 2006 11:59 am 
Hibernate Team
Hibernate Team

Joined: Tue Jun 13, 2006 11:29 pm
Posts: 315
Location: Calgary, Alberta, Canada
Hi all, I just want to report back on my findings on this.

There were a couple of things that I needed to figure out:

First, since I am using NHibernate, not Hibernate, I could not extend the Int32Type (as in example given in post #935678) because Int32Type has no public constructor. So I had to extend AbstractType instead (code to follow at the end). This added a bit more code, but I managed to get it to work.

Second, I didn't know where the user type are to be applied; on the <many-to-one>, or what? I discovered that it needs to be applied on the <id> element of the CHILD class, as in
Code:
   <class name="Child" table="Child_table">
      <id name="ID" column="ID" unsaved-value="0" type="MyAssembly.ZeroAsNullInt32IdentifierType, MyAssembly">
         <generator class="identity" />
      </id>
      ...
   </class>


Following is the entirety of the class. It will set Child.Parent to null if CHILD.ParentID is 0 in the database. On the flip side, it will write a 0 to CHILD.ParentID in the database when Child.Parent in the object is null. I hope it will help somebody.
Code:
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;

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

namespace PatientSys.Core.UserTypes
{
   /// <summary>
   /// see http://forum.hibernate.org/viewtopic.php?t=935678&highlight
   ///     http://forums.hibernate.org/viewtopic.php?t=933785
   /// </summary>
   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]);
            return ZERO.Equals(fieldValue) ? null : (object)fieldValue;
         }
      }

      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;
      }

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

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

      public override string ToString(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();
      }
   }
}


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 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.