Hello altogether,
Environment:
nHibernate version: 1.2.1.4000
Repository: M$ SQL Database Server 2005
VS2008, .NET 3.5, C#
Situation:
I'm trying to implement concurrency handling using nHibernate. I have a 'timestamp' column in a database table. As I read that nHibernate and timestamp column from SQL Server 2005 don't get along pretty well, I've implemented a custom IUserVersionType class and used it in my mapping like this:
Code:
<...>
<class name="MyTestProject.DomainModel.BusinessObjects.Document, MyTestProject.DomainModel"
table="Documents" dynamic-insert="true" dynamic-update="true" lazy="false" optimistic-lock="version">
<id name="Number" column="DocumentNumber" type="Int64" unsaved-value="0">
<generator class="identity"/>
</id>
<version name="RecordVersion" column="RecordTimestamp" generated="always" unsaved-value="null"
type="MyTestProject.DomainModel.nHibernate.UserTypes.SqlTimestamp, MyTestProject.DomainModel.nHibernate"/>
<...>
SqlTimestamp implementation:
Code:
public class SqlTimestamp :
IUserVersionType
{
#region IUserVersionType MEMBERS
public object Next( object current, ISessionImplementor session )
{
return current;
}
public object Seed( ISessionImplementor session )
{
return new byte[ 8 ];
}
#endregion //IUserVersionType MEMBERS
#region IUserType MEMBERS
public object Assemble( object cached, object owner )
{
return this.DeepCopy( cached );
}
public object DeepCopy( object value )
{
return value;
}
public object Disassemble( object value )
{
return this.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 Type ReturnedType
{
get { return typeof( byte[] ); }
}
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[ 1 ]{ new SqlType( DbType.Binary ) };
return types;
}
}
#endregion //IUserType MEMBERS
#region IComparer MEMBERS
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 //IComparer MEMBERS
}
Problem:
[RecordTimestamp] value is correctly loaded and handled by nHibernate with this implementation, BUT using 'dynamic-insert/update' attribute(s) nHibernate tries to update <version> property every time and thus my operation fails with an error:
System.Data.SqlClient.SqlException: Cannot update a timestamp column.What I want to accomplish:
Set property [RecordVersion] non-updatable via code.
Acknowledgments:
- override IInterceptor.FindDirty(..) -- IMHO to difficult for this case
- not use 'dynamic-insert/update' attribute(s) -- out of the question
Is there an elegant way in this situation to kindly tell nHibernate to never try to update [RecordTimestamp] value in the database?
Regards,
Mindaugas
P.S. By debugging my audit interceptor (implementation of EmptyInterceptor), I've noticed, that [RecordVersion] property value is the same for current state object and previous state object (setting breakpoint at FindDirty(..) and OnFlushDirty(..)). So why nHibernate includes it into an update statement? And why isn't overridden method Equals(..) or Compare(..) (in SqlTimestamp) get called (to decide is values are the same before including property into "to update" list)?