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.
