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.
