views:

470

answers:

1

I have legacy DB that store dates that means no-date as 9999-21-31, The column Till_Date is of type DateTime not-null="true".

in the application i want to build persisted class that represent no-date as null, So i used nullable DateTime in C# //public DateTime? TillDate {get; set; }

I created IUserType that knows to convert the entity null value to DB 9999-12-31

but it seems that NHibernate doesn't call SafeNullGet, SafeNullSet on my IUserType when the entity value is null, and report a null is used for not-null column.

I tried to by-pass it by mapping the column as not-null="false" (changed only the mapping file, not the DB) but it still didn't help, only now it tries to insert the null value to the DB and get ADOException.

Any knowledge if NHibernate doesn't support IUseType that convert null to not-null values?

Thanks

//Implementation before required fixes !!!

public class NullableDateTimeToNotNullUserType : IUserType
{
        private static readonly DateTime MaxDate = new DateTime(9999, 12, 31);

        public new bool Equals(object x, object y)
        {                                                   //This didn't work as well
            if (ReferenceEquals(x, y)) return true; //if(x == null && y == null) return false;

            if (x == null || y == null) return false;

            return x.Equals(y);
        }

        public int GetHashCode(object x)
        {
            return x == null ? 0 : x.GetHashCode();
        }

        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            var value = rs.GetDateTime(rs.GetOrdinal(names[0]));

            return (value == MaxDate)? null : value;
        }

        public void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            var dateValue = (DateTime?)value;

            var dbValue = (dateValue.HasValue) ? dateValue.Value : MaxDate;

            ((IDataParameter)cmd.Parameters[index]).Value = dbValue;
        }

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

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

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

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

        public SqlType[] SqlTypes
        {
            get { return new[] { NHibernateUtil.DateTime.SqlType }; }
        }

        public Type ReturnedType
        {
            get { return typeof(DateTime?); }
        }

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

//Final Implementation with fixes.

//Make the column mapping in hbm.xml not-null="false" even if in DB null not allowed.
//Make sure the class mapping in xml doesn't have dynamic-insert="true"

public class NullableDateTimeToNotNullUserType : IUserType
{
        private static readonly DateTime MaxDate = new DateTime(9999, 12, 31);

        public new bool Equals(object x, object y)
        {                                                   //This didn't work as well
            if (ReferenceEquals(x, y)) return true; //if(x == null && y == null) return false;

            if (x == null || y == null) return false;

            return x.Equals(y);
        }

        public int GetHashCode(object x)
        {
            return x == null ? 0 : x.GetHashCode();
        }

        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            var value = NHibernateUtil.Date.NullSafeGet(rs, names[0]);

            return (value == MaxDate)? default(DateTime?) : value;
        }

        public void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            var dateValue = (DateTime?)value;

            var dbValue = (dateValue.HasValue) ? dateValue.Value : MaxDate;

            NHibernateUtil.Date.NullSafeSet(cmd, valueToSet, index);
        }

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

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

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

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

        public SqlType[] SqlTypes
        {
            get { return new[] { NHibernateUtil.DateTime.SqlType }; }
        }

        public Type ReturnedType
        {
            get { return typeof(DateTime?); }
        }

        public bool IsMutable
        {
            get { return false; }
        }
    }
}
A: 

The problem is likely in your implementation of Equals. NHibernate uses Equals to determine if it needs to get or set the value. Please post your IUserType implementation.

Edit: OK, I think I see the problem. Try this:

public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
    var value = NHibernateUtil.Date.NullSafeGet(rs, names[0]);
    return (value == MaxDate) ? null : value;
}

public void NullSafeSet(IDbCommand cmd, object value, int index)
{
    var dateValue = (DateTime?)value;
    var dbValue = (dateValue.HasValue) ? dateValue.Value : MaxDate;
    NHibernateUtil.Date.NullSafeSet(cmd, valueToSet, index);
}

NullSafeGet may also need to cast the return value as DateTime?. Your implementation of Equals looks right.

Jamie Ide
using debugging i see that the call to equals pass null for both compared values.I tried 2 versions of Equals, regarding that:1st return true if compared values are null2nd return false if compared values are nullplease see added code implementationtnx
barakbbn
I adjusted things according to your code (added explicit casting in NullSafeGet)it works except in the following scenario,if in DB the date is not the max (ex. 2010-01-01)and i fetch the entity, change the date to null and save changes,then it call Equals(2010-01-01, null) which return false,but doesn't call NullSafeSet and throws exception of: NHibernate.PropertyValueException: not-null property references a null or transient value.I then changed the mapping to not-null="false" in the hbm.xml, and it worked this time NullSafeSet was called.Thanks for the help
barakbbn
I'm a little surprised that the not-null setting influences this, I thought that was for schema generation only. Please upvote and accept the answer if it helped.
Jamie Ide
It turned out that the problem still exists.But only happen if you try to persist new transient entitywith null value in the DateTime? property.It works OK when updating already persisted entity though.I was able to find what causes it, my class mapping xml haddynamic-insert="true". after changing it to falseAll worked OK.(I also have dynamic-update="true" but things works OK with it)P.S having not-null="true" in the property mapping still fail.
barakbbn