Mapping Timestamp Data Using NHibernate’s ICompositeUserType

In my previous post, I took some string data and mapped it directly to a boolean property on an entity. That was pretty simple, but I wanted to try it out on a little more complex object..

In our projects, most of the entities have a Timestamp property which is of type Timestamp:

public class User
{
    public string UserID { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public Timestamp Timestamp { get; set; }
}

and here’s the Timestamp class:

public class Timestamp
{
    public string CreatedByStaff { get; set; }
    public string UpdatedByStaff { get; set; }
    public DateTime? CreatedDateTime { get; set; }
    public DateTime? UpdatedDateTime { get; set; }
}

In the database, the USER table would have the obvious columns for UserID, Username, and Password, but also have the columns for the Timestamp which in my case are CREATED_BY_STAFF, UPDATED_BY_STAFF, CREATED_DATETIME, and UPDATED_DATETIME. Now this can easily be mapped as it is by using a component like so:

<component name="Timestamp" class="XYZ.Core.Timestamp, XYZ.Core">
  <property name="CreatedByStaff" type="String" column="CREATED_BY_STAFF" not-null="true"/>
  <property name="UpdatedByStaff" type="String" column="UPDATED_BY_STAFF" not-null="false"/>
  <property name="CreatedDateTime" type="DateTime" column="CREATED_DATETIME" not-null="true"/>
  <property name="UpdatedDateTime" type="DateTime" column="UPDATED_DATETIME" not-null="false"/>
</component>

That works well, but I hate having to repeat that all over the place. I would rather just have one single line that maps a Timestamp. I experimented with creating a custom mapping type using the ICompositeUserType and got a little closer to the goal. Here’s the class:

public class TimestampMappingType : ICompositeUserType
{
    public bool IsMutable
    {
        get { return true; }
    }

    public Type ReturnedClass
    {
        get { return typeof(Timestamp); }
    }

    public string[] PropertyNames
    {
        get { return new[] { "CreatedByStaff", "UpdatedByStaff", "CreatedDateTime", "UpdatedDateTime" }; }
    }

    public IType[] PropertyTypes
    {
        get { return new[] { NHibernateUtil.String, NHibernateUtil.String, NHibernateUtil.DateTime, NHibernateUtil.DateTime}; }
    }

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

    public object GetPropertyValue(object component, int property)
    {
        var timestamp = AsTimestamp(component);

        switch(property)
        {
            case 0:
                return timestamp.CreatedByStaff;
            case 1:
                return timestamp.UpdatedByStaff;
            case 2:
                return timestamp.CreatedDateTime;
            case 3:
                return timestamp.UpdatedDateTime;
            default:
                throw new YourException("No implementation for property index of '{0}'.", property);
        }
    }

    public void SetPropertyValue(object component, int property, object value)
    {
        if (component == null)
            throw new ArgumentNullException("component");

        var timestamp = AsTimestamp(component);
        switch (property)
        {
            case 0:
                timestamp.CreatedByStaff = (string)value;
                break;
            case 1:
                timestamp.UpdatedByStaff = (string)value;
                break;
            case 2:
                timestamp.CreatedDateTime = (DateTime?)value;
                break;
            case 3:
                timestamp.UpdatedDateTime = (DateTime?)value;
                break;
            default:
                throw new YourException("No implementation for property index of '{0}'.", property);
        }           
    }

    public object NullSafeGet(IDataReader dr, string[] names, ISessionImplementor session, object owner)
    {
        var createdByStaff = NHibernateUtil.String.NullSafeGet(dr, names[0]);
        var updatedByStaff = NHibernateUtil.String.NullSafeGet(dr, names[1]);
        var createdDateTime = NHibernateUtil.DateTime.NullSafeGet(dr, names[2]);
        var updatedDateTime = NHibernateUtil.DateTime.NullSafeGet(dr, names[3]);

        return new Timestamp
        {
            CreatedByStaff = (string)createdByStaff,
            UpdatedByStaff = (string)updatedByStaff,
            CreatedDateTime = (DateTime?)createdDateTime,
            UpdatedDateTime = (DateTime?)updatedDateTime
        };
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index, ISessionImplementor session)
    {
        if (value == null)
        {
            ((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
            ((IDataParameter)cmd.Parameters[index+1]).Value = DBNull.Value;
            ((IDataParameter)cmd.Parameters[index+2]).Value = DBNull.Value;
            ((IDataParameter)cmd.Parameters[index+3]).Value = DBNull.Value;
        }
        else
        {
            var timestamp = AsTimestamp(value);

            ((IDataParameter)cmd.Parameters[index]).Value = (object)timestamp.CreatedByStaff ?? DBNull.Value;
            ((IDataParameter)cmd.Parameters[index + 1]).Value = (object)timestamp.UpdatedByStaff ?? DBNull.Value;
            ((IDataParameter)cmd.Parameters[index + 2]).Value = (object)timestamp.CreatedDateTime ?? DBNull.Value;
            ((IDataParameter)cmd.Parameters[index + 3]).Value = (object)timestamp.UpdatedDateTime ?? DBNull.Value;
        }
    }

    public object DeepCopy(object value)
    {
        if(value == null) return null;

        var original = AsTimestamp(value);

        return new Timestamp
                   {
                        CreatedByStaff = original.CreatedByStaff, 
                        UpdatedByStaff = original.UpdatedByStaff,
                        CreatedDateTime = original.CreatedDateTime,
                        UpdatedDateTime = original.UpdatedDateTime
                    };
    }

    public object Disassemble(object value, ISessionImplementor session)
    {
        return DeepCopy(value);
    }

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

    public new bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, y)) return true;

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

        return x.Equals(y);
    }

    public int GetHashCode(object x)
    {
        return x == null ? typeof(Timestamp).GetHashCode() + 321 : x.GetHashCode();
    }

    private static Timestamp AsTimestamp(object value)
    {
        if (value == null) return null;

        var ts = value as Timestamp;

        if(ts == null)
            throw new YourException("Expected '{0}' but recieved '{1}'.", typeof(Timestamp), value.GetType());

        return ts;
    }
}

Using this class in my mapping, I can now shorten the Timestamp mapping to:

<property name="Timestamp" type="XYZ.DataAccess.TimestampMappingType, XYZ.DataAccess">
  <column name="CREATED_BY_STAFF" />
  <column name="UPDATED_BY_STAFF" />
  <column name="CREATED_DATETIME" />
  <column name="UPDATED_DATETIME" />
</property>

That’s a little better but I was hoping I could get away with not having to define the columns (they’re the same on every table) but I don’t see a way to set defaults (maybe I’m in the wrong place?). In the end, I’m not sure this really buys me much more than just mapping Timestamp as a component, but I’ll poke around a little more to see if I can figure it out.

Technorati Tags: ,

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Ray Houston

Ray is a software development leader and architect with 20 years hands-on experience. He enjoys finding elegant solutions to complex problems and delivering real value to customers. Ray is a speaker and contributor to community events.
This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

One Response to Mapping Timestamp Data Using NHibernate’s ICompositeUserType

  1. Trevor Westerdahl says:

    1) Why can’t you create an abstract base class and define the members in that class, then inherit that class for your other classes?

    2) How do you set the DateTime? It seems the proper way would require that a function be placed in the SQL (I.e. GetDate() in SQL Server) that way the DateTime reflects the true moment in time for the transaction, not some preassigned value.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>