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 Nov 17, 11:49 am, Mike Pontillo <[email protected]> wrote:
> 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
>
> On Wed, Nov 17, 2010 at 10:54 AM, Fabio Maulo <[email protected]> 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 <[email protected]> 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 Tue, Nov 16, 2010 at 9:14 PM, Mike Pontillo <[email protected]> wrote:
>
> >>> 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
>
> >>> On Thu, Nov 11, 2010 at 6:12 AM, Fabio Maulo <[email protected]>
> >>> 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 <[email protected]>
> >>> > 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 »

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

Reply via email to