Why does NHibernate throw a “GenericADOException: Unable to initialize” exception during ISession.Refresh in this case?

I had strange behavior (at least for me) with ISession.Refresh() .

I have an entity with a lazily loaded collection of children and a read-only property that falls into this collection, all of them are included in the second level cache. I use ISession.Refresh() in a long session to get the latest data after a transaction in the database and get the following error:

 NHibernate.Exceptions.GenericADOException : could not initialize a collection: [Test.NUnit.DBTest.TestModel.ParentTestEntity.Children#d4251363-cf88-4684-b65a-9f330107afcf][SQL: SELECT children0_.ParentTestEntity_id as ParentTe4_1_, children0_.Id as Id1_, children0_.Id as Id42_0_, children0_.RowVersion as RowVersion42_0_, children0_.Parent_id as Parent3_42_0_ FROM "ChildTestEntity" children0_ WHERE children0_.ParentTestEntity_id=?] ----> System.NullReferenceException : Object reference not set to an instance of an object. at NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type) at NHibernate.Loader.Collection.CollectionLoader.Initialize(Object id, ISessionImplementor session) at NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(Object key, ISessionImplementor session) at NHibernate.Event.Default.DefaultInitializeCollectionEventListener.OnInitializeCollection(InitializeCollectionEvent event) at NHibernate.Impl.SessionImpl.InitializeCollection(IPersistentCollection collection, Boolean writing) at NHibernate.Collection.AbstractPersistentCollection.Initialize(Boolean writing) at NHibernate.Collection.AbstractPersistentCollection.ReadSize() at NHibernate.Collection.PersistentBag.get_Count() DBTest\TestModel\EntiteTestCacheCollectionsParent.cs(25,0): at Test.NUnit.DBTest.TestModel.ParentTestEntity.get_Count() at (Object , GetterCallback ) at NHibernate.Bytecode.Lightweight.AccessOptimizer.GetPropertyValues(Object target) at NHibernate.Tuple.Entity.PocoEntityTuplizer.GetPropertyValuesWithOptimizer(Object entity) at NHibernate.Tuple.Entity.PocoEntityTuplizer.GetPropertyValues(Object entity) at NHibernate.Persister.Entity.AbstractEntityPersister.GetPropertyValues(Object obj, EntityMode entityMode) at NHibernate.Event.Default.AbstractVisitor.Process(Object obj, IEntityPersister persister) at NHibernate.Event.Default.DefaultRefreshEventListener.OnRefresh(RefreshEvent event, IDictionary refreshedAlready) at NHibernate.Event.Default.DefaultRefreshEventListener.OnRefresh(RefreshEvent event) at NHibernate.Impl.SessionImpl.FireRefresh(RefreshEvent refreshEvent) at NHibernate.Impl.SessionImpl.Refresh(Object obj) DBTest\NHibernateBehaviorTests.cs(610,0): at Test.NUnit.DBTest.NHibernateBehaviorTests.Test() --NullReferenceException at NHibernate.Engine.Loading.CollectionLoadContext.AddCollectionToCache(LoadingCollectionEntry lce, ICollectionPersister persister) at NHibernate.Engine.Loading.CollectionLoadContext.EndLoadingCollection(LoadingCollectionEntry lce, ICollectionPersister persister) at NHibernate.Engine.Loading.CollectionLoadContext.EndLoadingCollections(ICollectionPersister persister, IList`1 matchedCollectionEntries) at NHibernate.Engine.Loading.CollectionLoadContext.EndLoadingCollections(ICollectionPersister persister) at NHibernate.Loader.Loader.EndCollectionLoad(Object resultSetId, ISessionImplementor session, ICollectionPersister collectionPersister) at NHibernate.Loader.Loader.InitializeEntitiesAndCollections(IList hydratedObjects, Object resultSetId, ISessionImplementor session, Boolean readOnly) at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) at NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type) 

Here is a unit test that shows a problem with a simplified model:

  [Test] public void Test() { ISession session1 = NHibernateHelper.SessionFactory.OpenSession(); ISession session2 = NHibernateHelper.SessionFactory.OpenSession(); // Create a new entity tree and persist it ParentTestEntity parentSession1 = new ParentTestEntity(); parentSession1.AddChild(new ChildTestEntity()); session1.Save(parentSession1); session1.Flush(); // Load the saved object into another session ParentTestEntity parentSession2 = session2.Get<ParentTestEntity>(parentSession1.Id); session2.Refresh(parentSession2); // Throws here } 

Here are the participating objects:

 public class ParentTestEntity { public virtual Guid Id { get; private set; } public virtual long RowVersion { get; private set; } public virtual IList<ChildTestEntity> Children { get; protected set; } public ParentTestEntity() { this.Children = new List<ChildTestEntity>(); } public virtual int Count { get { return Children.Count; } set { } } public virtual void AddChild(ChildTestEntity child) { if (this.Children == null) { this.Children = new List<ChildTestEntity>(); } this.Children.Add(child); child.Parent = this; } } public class ChildTestEntity { public virtual Guid Id { get; private set; } public virtual long RowVersion { get; private set; } public virtual ParentTestEntity Parent { get; set; } } 

And their display:

 public class ParentTestEntityMap : ClassMap<ParentTestEntity> { public ParentTestEntityMap() { Cache.ReadWrite(); Id(x => x.Id) .GeneratedBy.GuidComb(); Version(x => x.RowVersion); HasMany(x => x.Children) .Inverse() .Cascade.All() .Cache.ReadWrite(); Map(x => x.Count); } } public class ChildTestEntityMap : ClassMap<ChildTestEntity> { public ChildTestEntityMap() { Cache.ReadWrite(); Id(x => x.Id) .GeneratedBy.GuidComb(); Version(x => x.RowVersion); References(x => x.Parent) .Not.Nullable(); } } 

During my tests, I found that:

  • Removing the display of the Count property,
  • removal of maps Cache.ReadWrite() ,
  • listing the collection before Refresh ,

enough for Refresh work correctly.

Does anyone know what I can do to update work?

Notes:

  • I can reproduce this behavior in both NHibernate 2.1.2 and 3.1.0,
  • I know that the empty setter in Count is ugly, it is here only to mirror the display of an object in a real model.
+7
source share
2 answers

Using Load() instead of Get() creates a problem.

This is not a general solution, since Load() has slightly different semantics and will throw if the corresponding line does not exist.

This is an acceptable limitation in our architecture, although we know that loaded objects exist in the database.

Any solution with Get() is still welcome.

+1
source

Refer to my answer to a similar question: "Cannot assign property value in entity constructor . " Here I will not go into details, because you can read it there.

Short answer: you get access to a virtual property in your constructor, which does not matter. The following change should fix the problem. Replace these lines ...

 public virtual IList<ChildTestEntity> Children { get; protected set; } public ParentTestEntity() { this.Children = new List<ChildTestEntity>(); } 

... with these lines:

 private IList<ChildTestEntity> _children; public virtual IList<ChildTestEntity> Children { get { return _children; } protected set { _children = value; } } public ParentTestEntity() { _children = new List<ChildTestEntity>(); } 

FxCop and ReSharper are two different tools that can automatically identify this problem.

0
source

All Articles