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; }
    }
<span style="color: #0000ff">public</span> Type ReturnedClass
{
    get { <span style="color: #0000ff">return</span> <span style="color: #0000ff">typeof</span>(Timestamp); }
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span>[] PropertyNames
{
    get { <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span>[] { <span style="color: #006080">"CreatedByStaff"</span>, <span style="color: #006080">"UpdatedByStaff"</span>, <span style="color: #006080">"CreatedDateTime"</span>, <span style="color: #006080">"UpdatedDateTime"</span> }; }
}

<span style="color: #0000ff">public</span> IType[] PropertyTypes
{
    get { <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span>[] { NHibernateUtil.String, NHibernateUtil.String, NHibernateUtil.DateTime, NHibernateUtil.DateTime}; }
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">object</span> Assemble(<span style="color: #0000ff">object</span> cached, ISessionImplementor session, <span style="color: #0000ff">object</span> owner)
{
    <span style="color: #0000ff">return</span> DeepCopy(cached);
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">object</span> GetPropertyValue(<span style="color: #0000ff">object</span> component, <span style="color: #0000ff">int</span> property)
{
    var timestamp = AsTimestamp(component);

    <span style="color: #0000ff">switch</span>(property)
    {
        <span style="color: #0000ff">case</span> 0:
            <span style="color: #0000ff">return</span> timestamp.CreatedByStaff;
        <span style="color: #0000ff">case</span> 1:
            <span style="color: #0000ff">return</span> timestamp.UpdatedByStaff;
        <span style="color: #0000ff">case</span> 2:
            <span style="color: #0000ff">return</span> timestamp.CreatedDateTime;
        <span style="color: #0000ff">case</span> 3:
            <span style="color: #0000ff">return</span> timestamp.UpdatedDateTime;
        <span style="color: #0000ff">default</span>:
            <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> YourException(<span style="color: #006080">"No implementation for property index of '{0}'."</span>, property);
    }
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> SetPropertyValue(<span style="color: #0000ff">object</span> component, <span style="color: #0000ff">int</span> property, <span style="color: #0000ff">object</span> <span style="color: #0000ff">value</span>)
{
    <span style="color: #0000ff">if</span> (component == <span style="color: #0000ff">null</span>)
        <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> ArgumentNullException(<span style="color: #006080">"component"</span>);

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

<span style="color: #0000ff">public</span> <span style="color: #0000ff">object</span> NullSafeGet(IDataReader dr, <span style="color: #0000ff">string</span>[] names, ISessionImplementor session, <span style="color: #0000ff">object</span> 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]);

    <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> Timestamp
    {
        CreatedByStaff = (<span style="color: #0000ff">string</span>)createdByStaff,
        UpdatedByStaff = (<span style="color: #0000ff">string</span>)updatedByStaff,
        CreatedDateTime = (DateTime?)createdDateTime,
        UpdatedDateTime = (DateTime?)updatedDateTime
    };
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> NullSafeSet(IDbCommand cmd, <span style="color: #0000ff">object</span> <span style="color: #0000ff">value</span>, <span style="color: #0000ff">int</span> index, ISessionImplementor session)
{
    <span style="color: #0000ff">if</span> (<span style="color: #0000ff">value</span> == <span style="color: #0000ff">null</span>)
    {
        ((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;
    }
    <span style="color: #0000ff">else</span>
    {
        var timestamp = AsTimestamp(<span style="color: #0000ff">value</span>);

        ((IDataParameter)cmd.Parameters[index]).Value = (<span style="color: #0000ff">object</span>)timestamp.CreatedByStaff ?? DBNull.Value;
        ((IDataParameter)cmd.Parameters[index + 1]).Value = (<span style="color: #0000ff">object</span>)timestamp.UpdatedByStaff ?? DBNull.Value;
        ((IDataParameter)cmd.Parameters[index + 2]).Value = (<span style="color: #0000ff">object</span>)timestamp.CreatedDateTime ?? DBNull.Value;
        ((IDataParameter)cmd.Parameters[index + 3]).Value = (<span style="color: #0000ff">object</span>)timestamp.UpdatedDateTime ?? DBNull.Value;
    }
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">object</span> DeepCopy(<span style="color: #0000ff">object</span> <span style="color: #0000ff">value</span>)
{
    <span style="color: #0000ff">if</span>(<span style="color: #0000ff">value</span> == <span style="color: #0000ff">null</span>) <span style="color: #0000ff">return</span> <span style="color: #0000ff">null</span>;

    var original = AsTimestamp(<span style="color: #0000ff">value</span>);

    <span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> Timestamp
               {
                    CreatedByStaff = original.CreatedByStaff, 
                    UpdatedByStaff = original.UpdatedByStaff,
                    CreatedDateTime = original.CreatedDateTime,
                    UpdatedDateTime = original.UpdatedDateTime
                };
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">object</span> Disassemble(<span style="color: #0000ff">object</span> <span style="color: #0000ff">value</span>, ISessionImplementor session)
{
    <span style="color: #0000ff">return</span> DeepCopy(<span style="color: #0000ff">value</span>);
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">object</span> Replace(<span style="color: #0000ff">object</span> original, <span style="color: #0000ff">object</span> target, ISessionImplementor session, <span style="color: #0000ff">object</span> owner)
{
    <span style="color: #0000ff">return</span> DeepCopy(original);
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">new</span> <span style="color: #0000ff">bool</span> Equals(<span style="color: #0000ff">object</span> x, <span style="color: #0000ff">object</span> y)
{
    <span style="color: #0000ff">if</span> (ReferenceEquals(x, y)) <span style="color: #0000ff">return</span> <span style="color: #0000ff">true</span>;

    <span style="color: #0000ff">if</span> (x == <span style="color: #0000ff">null</span> || y == <span style="color: #0000ff">null</span>) <span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span>;

    <span style="color: #0000ff">return</span> x.Equals(y);
}

<span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> GetHashCode(<span style="color: #0000ff">object</span> x)
{
    <span style="color: #0000ff">return</span> x == <span style="color: #0000ff">null</span> ? <span style="color: #0000ff">typeof</span>(Timestamp).GetHashCode() + 321 : x.GetHashCode();
}

<span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> Timestamp AsTimestamp(<span style="color: #0000ff">object</span> <span style="color: #0000ff">value</span>)
{
    <span style="color: #0000ff">if</span> (<span style="color: #0000ff">value</span> == <span style="color: #0000ff">null</span>) <span style="color: #0000ff">return</span> <span style="color: #0000ff">null</span>;

    var ts = <span style="color: #0000ff">value</span> <span style="color: #0000ff">as</span> Timestamp;

    <span style="color: #0000ff">if</span>(ts == <span style="color: #0000ff">null</span>)
        <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> YourException(<span style="color: #006080">"Expected '{0}' but recieved '{1}'."</span>, <span style="color: #0000ff">typeof</span>(Timestamp), <span style="color: #0000ff">value</span>.GetType());

    <span style="color: #0000ff">return</span> 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: ,
← Mapping Strings to Booleans Using NHibernate&#8217;s IUserType
A Simple Closure To Handle Try/Catch Around Transactions →