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:
NHibernate,
.NET
Posted
Mar 23 2008, 10:21 PM
by
Ray Houston