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

On Wed, Nov 17, 2010 at 3:14 PM, Fabio Maulo <[email protected]> wrote:
> 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
>
>
> El 17/11/2010, a las 20:09, Mike Pontillo <[email protected]> escribió:
>
>>   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.
>>
>
> --
> 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.
>
>

-- 
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