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();
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.