Row Version Not Being Updated as Expected

Skip to first unread message

JoAnnP38

unread,
Feb 18, 2009, 1:53:20 PM2/18/09
to nhusers
I am very new to using NHibernate; however, I have used other ORMs in
the past so hopefully I have a good understanding of most concepts. In
my first project I have created two entities (i.e Product, Theme)
which are mapped as follows:

<class name="AgileWorks.Model.Product" table="tPRODUCTS" optimistic-
lock="version">
<id name="Id" column="ID" type="Int32">
<generator class="identity" />
</id>
<version name="Timestamp" column="Timestamp" type="binary"
generated="always" unsaved-value="null" />
<property name="Name" not-null="true" />
<set name="Themes" lazy="true" cascade="all"
access="nosetter.camelcase">
<key column="ProductID" />
<one-to-many class="AgileWorks.Model.Theme" />
</set>
</class>

<class name="AgileWorks.Model.Theme" table="tTHEMES" optimistic-
lock="version">
<id name="Id" column="ID" type="Int32">
<generator class="identity" />
</id>
<version name="Timestamp" column="Timestamp" type="binary"
generated="always" unsaved-value="null" />
<property name="Name" not-null="true" />
<many-to-one name="Product" column="ProductID" cascade="all"
class="AgileWorks.Model.Product" not-null="true" />
</class>

I am very perplexed by the behavior of NHibernate whenever I execute
the following code:

Product product = new Product();
/* ... init product omitted */

Theme theme = new Theme();
/* ... init theme omitted */

product.AddTheme(theme);

using (ISession session = OpenSession())
{
session.Save(product); // SQL TRACE (1)
session.Flush(); // SQL TRACE (2)
session.Evict(theme);
Theme retrievedTheme = session.Get<Theme>(theme.Id);
/* At this point the content of byte[] retrievedTheme.Timestamp !=
theme.Timestamp !!!!
* See SQL Trace below
*/
}

After session.Save is execute above, SQL profiler shows the following
statements as being executed:

exec sp_executesql N'INSERT INTO tPRODUCTS (Name) VALUES (@p0);
select SCOPE_IDENTITY()',N'@p0 nvarchar(10)',@p0=N'My Product'

exec sp_executesql N'SELECT product_.Timestamp as Timestamp3_ FROM
tPRODUCTS product_ WHERE product_.ID=@p0',N'@p0 int',@p0=26

exec sp_executesql N'INSERT INTO tTHEMES (Name, ProductID) VALUES
(@p0, @p1); select SCOPE_IDENTITY()',N'@p0 nvarchar(30),@p1
int',@p0=N'My Theme',@p1=26

exec sp_executesql N'SELECT theme_.Timestamp as Timestamp4_ FROM
tTHEMES theme_ WHERE theme_.ID=@p0',N'@p0 int',@p0=12

When session.Flush() is called (or when I put this inside a
transaction and call commit instead of flush) the following SQL is
emitted:

exec sp_executesql N'UPDATE tTHEMES SET ProductID = @p0 WHERE ID =
@p1',N'@p0 int,@p1 int',@p0=26,@p1=12

However, there is no followup call to get the new value for the
Timestamp column!!!! So when the original theme is evicted from the
cache and then retrieved into another instand the timestamps don't
match. Since I have setup optimistic locking on the Timestamp property
I am assuming that if instead of evicting the original theme I had
tried to update and save it instead a locking exception would have
been generated. Also, why is ProductID being set again even though it
had been set correctly previously?

Can anyone comment on what is going on? Is my mapping incorrect? Since
I am very new to NHibernate I expect that there is something I've
overlooked or some trick I don't know.

A heaping amount of thanks to anyone who might be willing to discuss
this issue.

JoAnn Peeler

Fabio Maulo

unread,
Feb 18, 2009, 2:24:24 PM2/18/09
to nhu...@googlegroups.com
You are using a byte[] for a timestamp represented, probably, by a DateTime in your DB (you don't send us the trigger).
You can use:
<version name="Timestamp" type="Timestamp" unsaved-value="1/1/0001"/>

or
<version name="Timestamp" column="LastModified" type="YourBinaryUserTypeTimestampFullName, YourAssembly" generated="always" unsaved-value="null"/>

or in NH2.1.0
<typedef name="ByteTimestamp" class="YourBinaryUserTypeTimestampFullName, YourAssembly"/>
....
....
<version name="Timestamp" column="LastModified" type="ByteTimestamp" generated="always" unsaved-value="null"/>

Your user type should manage the comparison and equality of the bytearray.


2009/2/18 JoAnnP38 <joann....@gmail.com>



--
Fabio Maulo

JoAnnP38

unread,
Feb 18, 2009, 5:52:03 PM2/18/09
to nhusers
I'm sorry that I forgot to mention that I am using MS SQL Server and
my Timestamp is actually mapped to a field with SQL Server's timestamp
data type (or binary(8)). I am currently using version 2.0.1GA of
NHibernate and have previously used (instead of using the binary type)
a custom implementation IUserVersionType that supports SQL Server
timestamp/binary(8) fields. The mapping was working fine; however, it
exibit the exact same problem as I mention above -- that is that for
some reason NHibernate performs an update to table tTHEMES without
retrieving the latest value for the Timestamp field. Currently, I am
getting no complaints about the fact that I am using the binary type
for a row version in the mapping file, but I have no clue as to
whether NHibernate knows how to use this.

Can someone tell me why NHibernate is re-updating the ProductID in the
tTHEMES table (it was already set correctly in a previous SQL
statement) without retrieving the latest version (or in the case a SQL
Server timestamp?)

Also, where is the best place to start in order to develop a good
understanding of the ins-and-outs of NHibernate?

On Feb 18, 2:24 pm, Fabio Maulo <fabioma...@gmail.com> wrote:
> You are using a byte[] for a timestamp represented, probably, by a DateTime
> in your DB (you don't send us the trigger).You can use:
> <version name="Timestamp" type="Timestamp" unsaved-value="1/1/0001"/>
>
> or
> <version name="Timestamp" column="LastModified"
> type="YourBinaryUserTypeTimestampFullName, YourAssembly" generated="always"
> unsaved-value="null"/>
>
> or in NH2.1.0
> <typedef name="ByteTimestamp" class="YourBinaryUserTypeTimestampFullName,
> YourAssembly"/>
> ....
> ....
> <version name="Timestamp" column="LastModified" type="ByteTimestamp"
> generated="always" unsaved-value="null"/>
>
> Your user type should manage the comparison and equality of the bytearray.
>
> 2009/2/18 JoAnnP38 <joann.pee...@gmail.com>
> Fabio Maulo- Hide quoted text -
>
> - Show quoted text -

Fabio Maulo

unread,
Feb 19, 2009, 7:41:03 AM2/19/09
to nhusers
If you have a NUnit test please create a JIRA about your issue and
attach the test.

JoAnnP38

unread,
Feb 20, 2009, 12:54:29 PM2/20/09
to nhusers
Given the following mappings:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
default-cascade="save-update" auto-import="true"
assembly="NHibernate.Test"
namespace="NHibernate.Test.NHSpecificTest.NH1234" >
<class name="Bar" table="Bar" optimistic-lock="version">
<id name="Id" column="ID" type="Int32">
<generator class="identity" />
</id>
<version name="Timestamp" type="binary" generated="always" unsaved-
value="null" />
<property name="AField" not-null="true" />
<many-to-one name="Foo" column="FooID" cascade="all" class="Foo" not-
null="true" />
</class>
<class name="Foo" table="Foo" optimistic-lock="version">
<id name="Id" column="ID" type="Int32">
<generator class="identity" />
</id>
<version name="Timestamp" type="binary" generated="always" unsaved-
value="null" />
<property name="AField" not-null="true" />
<set name="Bars" lazy="true" cascade="all"
access="nosetter.camelcase">
<key column="FooID" />
<one-to-many class="Bar" />
</set>
</class>
</hibernate-mapping>

And using SQL Server 2005 the following test always fails using NH
2.0.1GA:

using (ISession session = OpenSession())
{
Bar retrievedBar = null;
Bar bar = new Bar();
bar.AField = 24;

Foo foo = new Foo();
foo.AField = 42;
foo.AddBar(bar);

session.Save(foo);
session.Flush();
session.Evict(bar);
session.Evict(foo);

retrievedBar = session.Get<Bar>(bar.Id);

// At this point the assumption is that bar and
retrievedBar should have
// identical values, but represent two different
POCOs. The asserts below
// are intended to verify this. Currently this test
fails on the comparison
// of the SQL Server timestamp (i.e. binary(8)) fields
because
// NHibernate does not retrieve the new timestamp
after the last update.

Assert.AreNotSame(bar, retrievedBar);
Assert.AreEqual(bar.Id, retrievedBar.Id);
Assert.AreEqual(bar.AField, retrievedBar.AField);
Assert.IsTrue(TimestampEquals(bar.Timestamp,
retrievedBar.Timestamp),
"Timestamps are different!");
}

As of yet, I have not been able to get this test running in the
NHibernate.Test project due to a failure while compling the mappings.
I have started another thread concerning this, so I don't want to
discuss that here; however, my assumption is that if I save an object,
evict the object from the cache and then retrieve another copy of the
object then the values should be identical. When you have a database
generated version field (BTW, I have also verified that this test
fails with an Int64 version instead of a SQL server timestamp version)
NHibernate, for whatever reason, doesn't retrieve the timestamp after
the last update when the above code is executed. If instead of failing
the insert, I tried to update and save the original Bar object above,
NHibernate throws an exception (as it should) whenever it detects the
versions are no longer in sync.

Maybe someone can answer this question for me, is it not typical that
NHibernate is used with database designs that generate versioning at
the database level?
> > > - Show quoted text -- Hide quoted text -

Fabio Maulo

unread,
Feb 20, 2009, 1:49:10 PM2/20/09
to nhu...@googlegroups.com
Please create a JIRA coping this mail. Thanks.

2009/2/20 JoAnnP38 <joann....@gmail.com>



--
Fabio Maulo

JoAnnP38

unread,
Feb 20, 2009, 5:14:18 PM2/20/09
to nhusers
Done -- NH-1685

http://nhjira.koah.net/browse/NH-1685

On Feb 20, 1:49 pm, Fabio Maulo <fabioma...@gmail.com> wrote:
> Please create a JIRA coping this mail. Thanks.
>
> 2009/2/20 JoAnnP38 <joann.pee...@gmail.com>
> ...
>
> read more »- Hide quoted text -
Reply all
Reply to author
Forward
0 new messages