SQL Server Timestamp field and Fluent-NHibernate
This morning I was trying to do a Version mapping with Fluent-NHibernate on a timestamp column in a SQL database, this didn’t work so good right out of the box so I dug around and found some information here and there and once I pieced it together it all worked.
First off the mapping:
public class ProjectMap : ClassMap<Project>, IMapGenerator
{
public ProjectMap()
{
WithTable("Projects");
Id(x => x.Id)
.GeneratedBy
.GuidComb()
.WithUnsavedValue("00000000-0000-0000-0000-000000000000");
Map(x => x.Name)
.WithLengthOf(50)
.CanNotBeNull();
Map(x => x.Started);
Map(x => x.Ended);
Map(x => x.Description);
Version(x => x.TimeStamp)
.TheColumnNameIs("LastChanged");
}
}
public override VersionPart Version(System.Linq.Expressions.Expression<Func<ProjectResource, object>> expression)
{
var versionPart = new VersionPart(ReflectionHelper.GetProperty(expression));
versionPart.SetAttribute("type", "ProjectTracker.Library.Mapping.UserTypeTimestamp, ProjectTracker.Library");
versionPart.SetAttribute("generated", "always");
versionPart.SetAttribute("unsaved-value", "null");
AddPart(versionPart);
return versionPart;
}
The main thing here is the overridden Version method. What this override is doing is setting up a few attributes required to make this all work properly, I think most of them are self explanetory. Inside this method is a set attribute method that sets the type of the timestamp to UserTypeTimestamp. The coded for this class follows below:
UserTypeTimestamp Class:
public class UserTypeTimestamp : IUserVersionType
{
#region IUserVersionType Members
public object Next(object current, ISessionImplementor session)
{
return current;
}
public object Seed(ISessionImplementor session)
{
return new byte[8];
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object DeepCopy(object value)
{
return value;
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public bool IsMutable
{
get { return false; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
return rs.GetValue(rs.GetOrdinal(names[0]));
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
NHibernateUtil.Binary.NullSafeSet(cmd, value, index);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public System.Type ReturnedType
{
get { return typeof(byte[]); }
}
public SqlType[] SqlTypes
{
get { return new SqlType[] { new SqlType(DbType.Binary) }; }
}
public int Compare(object x, object y)
{
byte[] xbytes = (byte[])x;
byte[] ybytes = (byte[])y;
if (xbytes.Length < ybytes.Length)
{
return -1;
}
if (xbytes.Length > ybytes.Length)
{
return 1;
}
for (int i = 0; i < xbytes.Length; i++)
{
if (xbytes[i] < ybytes[i])
{
return -1;
}
if (xbytes[i] > ybytes[i])
{
return 1;
}
}
return 0;
}
bool IUserType.Equals(object x, object y)
{
return (x == y);
}
#endregion
}
Now for the Business object change this:
private byte[] timeStamp = new byte[];
To this:
private byte[] timeStamp;
internal byte[] TimeStamp
{
get { return timeStamp; }
set { timeStamp = value;}
}
With the addition of the class and an internal Property so NHibernate can access the data in the timestamp everything works pefectly to implement optimistic concurrency with Fluent-NHibernate and SQL Server timestamp fields.