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.
