NullReferenceException with version properties

872 views
Skip to first unread message

Mike Pontillo

unread,
Nov 9, 2010, 8:30:18 PM11/9/10
to nhusers
Greetings,

I am using NHibernate 3.0.0 beta 2, and am trying to evolve some
prototype code that was using the "session per call" anti-pattern to
use a "session per request" approach. I soon noticed that after
implementing the session sharing, my unit tests started failing with
the following exception:

System.NullReferenceException : Object reference not set to an
instance of an object.
at NHibernate.Type.Int32Type.Next(Object current, ISessionImplementor
session) in d:\CSharp\NH\nhibernate\src\NHibernate\Type\Int32Type.cs:
line 77
at NHibernate.Engine.Versioning.Increment(Object version, IVersionType
versionType, ISessionImplementor session) in d:\CSharp\NH\nhibernate
\src\NHibernate\Engine\Versioning.cs: line 31
at
NHibernate.Event.Default.DefaultFlushEntityEventListener.GetNextVersion(FlushEntityEvent
event) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default
\DefaultFlushEntityEventListener.cs: line 331
at
NHibernate.Event.Default.DefaultFlushEntityEventListener.ScheduleUpdate(FlushEntityEvent
event) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default
\DefaultFlushEntityEventListener.cs: line 242
at
NHibernate.Event.Default.DefaultFlushEntityEventListener.OnFlushEntity(FlushEntityEvent
event) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default
\DefaultFlushEntityEventListener.cs: line 45
at
NHibernate.Event.Default.AbstractFlushingEventListener.FlushEntities(FlushEvent
event) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default
\AbstractFlushingEventListener.cs: line 161
at
NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent
event) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default
\AbstractFlushingEventListener.cs: line 60
at
NHibernate.Event.Default.DefaultDirtyCheckEventListener.OnDirtyCheck(DirtyCheckEvent
event) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default
\DefaultDirtyCheckEventListener.cs: line 21
at NHibernate.Impl.SessionImpl.IsDirty() in d:\CSharp\NH\nhibernate\src
\NHibernate\Impl\SessionImpl.cs: line 1510

The failures happened at "random" times, such as when I was about
to execute a query and NHibernate would do a dirty check. So I added
asserts for session.IsDirty() to try to catch the problem earlier, but
I still don't see an obvious cause. The odd thing is, the version
property should not be incrementing, since I am only doing read-only
work within the session so far. For example, the following code fails:


Assert.IsFalse(session.IsDirty());
var query = session.CreateQuery("from " + typeof(T).Name);
var list = query.List<T>();
Console.WriteLine(" HQL query: {0} " + typeof(T).Name + "
objects found", list.Count());
Assert.AreEqual(_rowCount, list.Count()); // value cached
in test setup
Assert.IsFalse(session.IsDirty());

I have been looking at this all afternoon, and tried to recreate
the problem by pasting similar unit test code into the
NHibernate.Test.VersionTest unit tests. (No luck yet.) Also, I tried
doing a "session.Clear()" before running this code, (which I thought
might solve the problem if there was stale data in the session) but it
had no effect.

I'm running out of ideas... does anyone have any thoughts on what
to look at next?

Thanks,
Mike

Mike Pontillo

unread,
Nov 10, 2010, 6:04:24 PM11/10/10
to nhusers
In case anyone was wondering,

I figured out the problem. I am trying to map a legacy database
that uses version columns that are nullable. NHibernate threw
exceptions when I defined the nullable types in my POCOs and the
NHibernate XML (even though the manual stated that they were supported
-- so likely a bug) so I defined them as "int". However, I didn't
notice that some rows (very few - likely mistakes, or cases where
someone hand-edited the data) in the database indeed have NULL values
for the version column. If a query ever cached one of these NULL
values, and NHibernate subsequently performed a dirty check, it will
throw this exception.

If I have time, I'll write up a test case and try patching the code
so NHibernate supports nullable version columns better. I think if
NHibernate treated NULL version columns as if they had the value 0,
this would fix the problem.

Regards,
Mike

Fabio Maulo

unread,
Nov 11, 2010, 9:12:34 AM11/11/10
to nhu...@googlegroups.com
May be you have to know that you can implements your own type for the
version property following the rules of your legacy DB.

--
Fabio Maulo

> --
> You received this message because you are subscribed to the Google Groups "nhusers" group.
> To post to this group, send email to nhu...@googlegroups.com.
> To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.
>

Mike Pontillo

unread,
Nov 16, 2010, 7:14:51 PM11/16/10
to nhu...@googlegroups.com
Hi Fabio,

Thanks for the response. A custom type seemed like overkill here
since all I really want is a nullable Int32. I ended up doing three
things to work around this problem:

- Made the version property in my POCO nullable (int?) to solve the
problem where NHibernate found a "dirty" (but not really dirty) object
in the database, since its version property was mistakenly set to null
- Made the version property in my mapping XML just an "int" (not sure
if it necessary to call out that it's nullable in the mapping XML, but
I got the exception noted below when I did -- it works when I leave
out the type as well, of course.)
- Made the following code change (to fix the NullReferenceException
when NHibernate tries to increment the null value in the database):

--- Versioning.cs
+++ Versioning.cs (working copy)
@@ -28,6 +28,11 @@
/// <returns>Returns the next value for the version.</returns>
public static object Increment(object version,
IVersionType versionType, ISessionImplementor session)
{
+ if(version == null)
+ {
+ version = versionType.Seed(session);
+ }
+
object next = versionType.Next(version, session);
if (log.IsDebugEnabled)
{

By the way, I also tested to verify that the 3rd change was really
necessary. (Since I saw Seed() being used elsewhere, I wasn't sure if
it would try again to increment a null value after I fixed my
mappings.)

Also, in case anyone else is seeing this problem, the following
exception is thrown when I try to specify the type of the version
property in the XML as "int?":

NHibernate.MappingException : Could not compile the mapping document:
Example.hbm.xml
----> NHibernate.MappingException : Could not determine type for:
MyCompany.Model.int?, PROLIN.DAO, for columns:
NHibernate.Mapping.Column(VERSION)
at NHibernate.Cfg.Configuration.LogAndThrow(Exception exception) in
Configuration.cs: line 340
at NHibernate.Cfg.Configuration.AddDeserializedMapping(HbmMapping
mappingDocument, String documentFileName) in Configuration.cs: line
528
at NHibernate.Cfg.Configuration.AddValidatedDocument(NamedXmlDocument
doc) in Configuration.cs: line 497
at NHibernate.Cfg.Configuration.ProcessMappingsQueue() in
Configuration.cs: line 1830
at NHibernate.Cfg.Configuration.AddDocumentThroughQueue(NamedXmlDocument
document) in Configuration.cs: line 1821
at NHibernate.Cfg.Configuration.AddXmlReader(XmlReader hbmReader,
String name) in Configuration.cs: line 1814
at NHibernate.Cfg.Configuration.AddInputStream(Stream xmlInputStream,
String name) in Configuration.cs: line 644
at NHibernate.Cfg.Configuration.AddResource(String path, Assembly
assembly) in Configuration.cs: line 682
at NHibernate.Cfg.Configuration.AddAssembly(Assembly assembly) in
Configuration.cs: line 761
--MappingException
at NHibernate.Mapping.SimpleValue.get_Type() in SimpleValue.cs: line 241
at NHibernate.Cfg.XmlHbmBinding.RootClassBinder.BindProperty(HbmVersion
versionSchema, Property property, IDictionary`2 inheritedMetas) in
RootClassBinder.cs: line 227
at NHibernate.Cfg.XmlHbmBinding.RootClassBinder.BindVersion(HbmVersion
versionSchema, PersistentClass rootClass, Table table, IDictionary`2
inheritedMetas) in RootClassBinder.cs: line 209
at NHibernate.Cfg.XmlHbmBinding.RootClassBinder.Bind(HbmClass
classSchema, IDictionary`2 inheritedMetas) in RootClassBinder.cs: line
55
at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddRootClasses(HbmClass
rootClass, IDictionary`2 inheritedMetas) in MappingRootBinder.cs: line
83
at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.AddEntitiesMappings(HbmMapping
mappingSchema, IDictionary`2 inheritedMetas) in MappingRootBinder.cs:
line 42
at NHibernate.Cfg.XmlHbmBinding.MappingRootBinder.Bind(HbmMapping
mappingSchema) in MappingRootBinder.cs: line 29
at NHibernate.Cfg.Configuration.AddDeserializedMapping(HbmMapping
mappingDocument, String documentFileName) in Configuration.cs: line
520

This exception may be an error on my part rather than a bug, since
I am not explicitly calling out nullable properties anywhere else in
my mapping XML. (I imagine I was trying to do something unsupported,
but I don't explicitly state types anywhere else in my mapping XML.)
The quirk here is that for a version property (according to the
reference manual I found at
http://www.nhforge.org/doc/nh/en/index.html, section 5.1.7), the
"type" parameter is "(optional - defaults to Int32)". I'm not sure why
this wouldn't default to the type defined in the POCO for the version
property. Also, the manual states "Version numbers may be of type
Int64, Int32, Int16, Ticks, Timestamp, or TimeSpan (or their nullable
counterparts in .NET 2.0)", so I assumed I could write "int?".

Regards,
Mike

Mike Pontillo

unread,
Nov 16, 2010, 8:05:11 PM11/16/10
to nhu...@googlegroups.com
Oops,

Ignore the "fixed" code, it was still half-baked, and there were a
few problems with it. First, I didn't notice that
AbstractEntityPersister.ForceVersionIncrement() also would need this
same check, and that the version update would actually fail (because
WHERE version = 0 isn't the same as WHERE version is null).

Next I tried another approach (change all the .Next() methods on
the integer types -- see attached patch) but that causes a different
exception. (see below my signature).

I think the real fix involves the default unsaved-value being both
NULL and 0 for nullable types (or a special case somewhere around
this), which I'm not sure how to accomplish...

Regards,
Mike

failed: NHibernate.StaleObjectStateException : Row was updated or
deleted by another transaction (or unsaved-value mapping was
incorrect): [MyCompany.Example#42]
Persister\Entity\AbstractEntityPersister.cs(2172,0): at
NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32 rows,
Object id, Int32 tableNumber, IExpectation expectation, IDbCommand
statement)
Persister\Entity\AbstractEntityPersister.cs(1618,0): at
NHibernate.Persister.Entity.AbstractEntityPersister.ForceVersionIncrement(Object
id, Object currentVersion, ISessionImplementor session)
Event\Default\AbstractLockUpgradeEventListener.cs(64,0): at
NHibernate.Event.Default.AbstractLockUpgradeEventListener.UpgradeLock(Object
entity, EntityEntry entry, LockMode requestedLockMode,
ISessionImplementor source)
Event\Default\DefaultLockEventListener.cs(55,0): at
NHibernate.Event.Default.DefaultLockEventListener.OnLock(LockEvent
event)
Impl\SessionImpl.cs(2470,0): at
NHibernate.Impl.SessionImpl.FireLock(LockEvent lockEvent)
Impl\SessionImpl.cs(776,0): at
NHibernate.Impl.SessionImpl.Lock(Object obj, LockMode lockMode)

versionTypes.patch

Fabio Maulo

unread,
Nov 17, 2010, 1:44:09 PM11/17/10
to nhu...@googlegroups.com
The implementation of a custom type is not an overkill, instead it is just the way NH gives you to define what is correct/intelligent for you.
For me Int32 is more than enough and its default "unsaved-value" as zero is more than enough. You are looking for a version with two possible unsaved-value ('null' and zero)
Fabio Maulo

Fabio Maulo

unread,
Nov 17, 2010, 1:54:00 PM11/17/10
to nhu...@googlegroups.com
ah... The Versioning is not the place of the modification, instead, the right place is the implementation of IVersionType inside Int32Type.
btw... a IUserVersionType would be perfect for your case.
--
Fabio Maulo

Mike Pontillo

unread,
Nov 17, 2010, 2:49:50 PM11/17/10
to nhu...@googlegroups.com
Thanks,

Yes, it seems I will need a custom type, if only for the null-safe
.Next() method. I looked at the interface definition and didn't see
anything else that I really need to override.

As for multiple unsaved-values, I checked the IsUnsaved() method in
the VersionValue class and noticed that it checks for either null, or
the value. So I think that case is covered if I set unsaved-value="0"
in the XML.

Now the problem I am running into is different: the version update
fails. When I show the SQL I see something like this:

NHibernate: UPDATE dbo.[EXAMPLE] SET [VERSION] = @p0 WHERE [ID] = @p1
AND [VERSION] = @p2;@p0 = 1 [Type: Int32 (0)], @p1 = 42 [Type: Decimal
(0)], @p2 = NULL [Type: Int32 (0)]

... then NHibernate notices that while it expected one row to be
updated, zero rows were updated. (see stack trace below my signature)

I'm not sure if this is the real problem, but shouldn't the UPDATE
in this case be generated with "VERSION is NULL", not a "VERSION =
<some-value>"? In the SQL I see VERSION = @p2, where @p2 is the NULL
Int32 object. I thought that the NullSafeSet() was intended to handle
this case (called in ForceVersionIncrement() in
AbstractEntityPersister) but I wasn't sure how this happens. (Does
.NET take care of changing this to an "is NULL" statement?)

I ran the same SQL manually and it updates the row, but only if I
write "is null".

Regards,
Mike


NHibernate.StaleObjectStateException : Row was updated or deleted by
another transaction (or unsaved-value mapping was incorrect):
[MyCompany.Example#42]

----> NHibernate.StaleStateException : Unexpected row count: 0; expected: 1


at NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32
rows, Object id, Int32 tableNumber, IExpectation expectation,

IDbCommand statement) in AbstractEntityPersister.cs: line 2174
at NHibernate.Persister.Entity.AbstractEntityPersister.ForceVersionIncrement(Object
id, Object currentVersion, ISessionImplementor session) in
AbstractEntityPersister.cs: line 1618


at NHibernate.Event.Default.AbstractLockUpgradeEventListener.UpgradeLock(Object
entity, EntityEntry entry, LockMode requestedLockMode,

ISessionImplementor source) in AbstractLockUpgradeEventListener.cs:
line 64
at NHibernate.Event.Default.DefaultLockEventListener.OnLock(LockEvent
event) in DefaultLockEventListener.cs: line 55
at NHibernate.Impl.SessionImpl.FireLock(LockEvent lockEvent) in
SessionImpl.cs: line 2470
at NHibernate.Impl.SessionImpl.Lock(Object obj, LockMode lockMode) in
SessionImpl.cs: line 776
--StaleStateException
at NHibernate.AdoNet.Expectations.BasicExpectation.VerifyOutcomeNonBatched(Int32
rowCount, IDbCommand statement) in Expectations.cs: line 33


at NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32
rows, Object id, Int32 tableNumber, IExpectation expectation,

IDbCommand statement) in AbstractEntityPersister.cs: line 2163

Mike Pontillo

unread,
Nov 17, 2010, 6:09:27 PM11/17/10
to nhusers
I have confirmed that this is the problem. (there "where" clause on
the update can't handle a NULL version property) I see that in another
part of AbstractEntityPersister.cs, this case is handled; for example:

updateBuilder.AddWhereFragment(_propertyColumnNames[k] + " is
null");

I tried changing the code to handle this case, but the change
quickly got much larger than I wanted. There seems to be the
assumption (in several places) that the version property can never be
null, including AbstractEntityPersister.GenerateUpdateString(), which
is called at init time and [seems to?] cache the SQL for doing an
update. This includes the "where" clause which assumes that the
version property is not null.

There is also a call to SetVersionColumn() in the
UpdateLockingStrategy class (GenerateLockString()) that I'm not sure
what to do with, as this would also depend on the value of the version
property.

I am guessing it was done this way for performance reasons? If
every update needed to check for this obscure corner case, it might
become significant.

Regards,
Mike
> On Wed, Nov 17, 2010 at 10:54 AM, Fabio Maulo <fabioma...@gmail.com> wrote:
> > ah... The Versioning is not the place of the modification, instead, the
> > right place is the implementation of IVersionType inside Int32Type.
> > btw... a IUserVersionType would be perfect for your case.
>
> > On Wed, Nov 17, 2010 at 3:44 PM, Fabio Maulo <fabioma...@gmail.com> wrote:
>
> >> The implementation of a custom type is not an overkill, instead it is just
> >> the way NH gives you to define what is correct/intelligent for you.
> >> For me Int32 is more than enough and its default "unsaved-value" as zero
> >> is more than enough. You are looking for a version with two possible
> >> unsaved-value ('null' and zero)
> >>> On Thu, Nov 11, 2010 at 6:12 AM, Fabio Maulo <fabioma...@gmail.com>
> >>> wrote:
> >>> > May be you have to know that you can implements your own type for the
> >>> > version property following the rules of your legacy DB.
>
> >>> > --
> >>> > Fabio Maulo
>
> >>> > El 10/11/2010, a las 20:04, Mike Pontillo <ponti...@gmail.com>
> >>> > escribió:
>
> >>> >> In case anyone was wondering,
>
> >>> >>   I figured out the problem. I am trying to map a legacy database
> >>> >> that uses version columns that are nullable. NHibernate threw
> >>> >> exceptions when I defined the nullable types in my POCOs and the
> >>> >> NHibernate XML (even though the manual stated that they were supported
> >>> >> -- so likely a bug) so I defined them as "int". However, I didn't
> >>> >> notice that some rows (very few - likely mistakes, or cases where
> >>> >> someone hand-edited the data) in the database indeed have NULL values
> >>> >> for the version column. If a query ever cached one of these NULL
> >>> >> values, and NHibernate subsequently performed a dirty check, it
>
> ...
>
> read more »

Fabio Maulo

unread,
Nov 17, 2010, 6:14:29 PM11/17/10
to nhu...@googlegroups.com
That is because the Seed should return a valid value and not a null.
For integer based entities the Seed returns 1, that mean that a null
can't be the previous value stored for version. A null can be a value
for "unsaved" but not the actual value for a stored state.

--
Fabio Maulo

Mike Pontillo

unread,
Nov 17, 2010, 6:22:34 PM11/17/10
to nhu...@googlegroups.com
Hi Fabio,

In my case, .Seed() returns 1. The problem is that version columns
*already stored* (in the legacy DB I am mapping) have "null" values in
them, not rows newly created by NHibernate.

At this point, I think the path of least resistance would be to
write some SQL to update all the tables, before I start using
NHibernate with this database. (I was hoping NHibernate could handle
this for me seamlessly, but if version properties are already NULL in
the database, I start running into this bug.)

Regards,
Mike

Mike Pontillo

unread,
Nov 17, 2010, 8:19:52 PM11/17/10
to nhu...@googlegroups.com
In case it helps anyone,

The workaround I came up with was to run some code like this after
I generated the mappings, but before I use them:

private static void FixNullVersionPropertiesForClass(
ISession session, PersistentClass mapping)
{
if (mapping.IsVersioned)
{
var command = session.Connection.CreateCommand();
var dialect = session.GetDialect();
var versionPropertyName =
mapping.Version.ColumnIterator.First().GetText(dialect);
string tableName = mapping.Table.GetQualifiedName(dialect);

command.CommandText =
"update " + tableName +
" set " + versionPropertyName + " = 1 " +
"where " + versionPropertyName + " is null";

session.Transaction.Enlist(command);

command.ExecuteNonQuery();
}
}

I just iterate through all the mappings in the configuration and
run this to fix things up. (Note, the session must have an open
transaction that will be committed outside this function.) An ideal
solution would also add NOT NULL constraints. (and never run more than
once)

Regards,
Mike

Reply all
Reply to author
Forward
0 new messages