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