-
Notifications
You must be signed in to change notification settings - Fork 930
Description
After updating from NHibernate 5.4.7 to 5.5.0 some of the tests in my application started failing. It looks like this is because of an undocumented breaking change.
NHibernate 5.5.0 now calls the GetHashCode method of entities which are implicitly saved by having a cascaded save from an entity that is saved by calling session.Save. Simplified model:
// An object of this class is passed to 'session.Save'
public class ParentEntity
{
// Mapped with a sequence generator
public virtual long Id { get; set; }
// Mapped as a set with 'all-delete-orphan'
public virtual ICollection<SubEntity> SubEntities { get; set; }
}
public class SubEntity
{
// Mapped with a sequence generator
public virtual long Id { get; set; }
}The GetHashCode method of SubEntity is called in this case.
The call stack looks like this:
MyNamespace.Infrastructure.Entity<long>.GetHashCode()
System.Collections.Generic.ObjectEqualityComparer<System.__Canon>.GetHashCode(System.__Canon obj)
System.Collections.Generic.HashSet<MyEntityType>.InternalGetHashCode(MyEntityType item)
System.Collections.Generic.HashSet<MyEntityType>.AddIfNotPresent(MyEntityType value)
System.Collections.Generic.HashSet<MyEntityType>.UnionWith(System.Collections.Generic.IEnumerable<MyEntityType> other)
System.Collections.Generic.HashSet<MyEntityType>.HashSet(System.Collections.Generic.IEnumerable<MyEntityType> collection, System.Collections.Generic.IEqualityComparer<MyEntityType> comparer)
NHibernate.Type.GenericSetType<MyEntityType>.Wrap(NHibernate.Engine.ISessionImplementor session, object collection)
NHibernate.Event.Default.WrapVisitor.ProcessArrayOrNewCollection(object collection, NHibernate.Type.CollectionType collectionType)
NHibernate.Event.Default.WrapVisitor.ProcessComponent(object component, NHibernate.Type.IAbstractComponentType componentType)
NHibernate.Event.Default.WrapVisitor.ProcessValue(int i, object[] values, NHibernate.Type.IType[] types)
NHibernate.Event.Default.AbstractVisitor.ProcessEntityPropertyValues(object[] values, NHibernate.Type.IType[] types)
NHibernate.Event.Default.AbstractSaveEventListener.VisitCollectionsBeforeSave(object entity, object id, object[] values, NHibernate.Type.IType[] types, NHibernate.Event.IEventSource source)
NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(object entity, NHibernate.Engine.EntityKey key, NHibernate.Persister.Entity.IEntityPersister persister, bool useIdentityColumn, object anything, NHibernate.Event.IEventSource source, bool requiresImmediateIdAccess)
NHibernate.Event.Default.AbstractSaveEventListener.PerformSave(object entity, object id, NHibernate.Persister.Entity.IEntityPersister persister, bool useIdentityColumn, object anything, NHibernate.Event.IEventSource source, bool requiresImmediateIdAccess)
NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(object entity, string entityName, object anything, NHibernate.Event.IEventSource source, bool requiresImmediateIdAccess)
NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(NHibernate.Event.SaveOrUpdateEvent event)
NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(NHibernate.Event.SaveOrUpdateEvent event)
NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(NHibernate.Event.SaveOrUpdateEvent event)
NHibernate.Impl.SessionImpl.FireSave(NHibernate.Event.SaveOrUpdateEvent event)
NHibernate.Impl.SessionImpl.Save(object obj)With NHibernate 5.4.7 this call to GetHashCode via session.Save didn't happen at all! Instead GetHashCode is only called by session.Flush with this call stack:
MyNamespace.Infrastructure.Entity<long>.GetHashCode()
System.Collections.Generic.ObjectEqualityComparer<System.__Canon>.GetHashCode(System.__Canon obj)
System.Collections.Generic.HashSet<MyEntityType>.InternalGetHashCode(MyEntityType item)
System.Collections.Generic.HashSet<MyEntityType>.AddIfNotPresent(MyEntityType value)
System.Collections.Generic.HashSet<MyEntityType>.UnionWith(System.Collections.Generic.IEnumerable<MyEntityType> other)
System.Collections.Generic.HashSet<MyEntityType>.HashSet(System.Collections.Generic.IEnumerable<MyEntityType> collection, System.Collections.Generic.IEqualityComparer<MyEntityType> comparer)
NHibernate.Type.GenericSetType<MyEntityType>.Wrap(NHibernate.Engine.ISessionImplementor session, object collection)
NHibernate.Event.Default.WrapVisitor.ProcessArrayOrNewCollection(object collection, NHibernate.Type.CollectionType collectionType)
NHibernate.Event.Default.WrapVisitor.ProcessComponent(object component, NHibernate.Type.IAbstractComponentType componentType)
NHibernate.Event.Default.WrapVisitor.ProcessValue(int i, object[] values, NHibernate.Type.IType[] types)
NHibernate.Event.Default.AbstractVisitor.ProcessEntityPropertyValues(object[] values, NHibernate.Type.IType[] types)
NHibernate.Event.Default.DefaultFlushEntityEventListener.WrapCollections(NHibernate.Event.IEventSource session, NHibernate.Persister.Entity.IEntityPersister persister, NHibernate.Type.IType[] types, object[] values)
NHibernate.Event.Default.DefaultFlushEntityEventListener.OnFlushEntity(NHibernate.Event.FlushEntityEvent event)
NHibernate.Event.Default.AbstractFlushingEventListener.FlushEntities(NHibernate.Event.FlushEvent event)
NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(NHibernate.Event.FlushEvent event)
NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(NHibernate.Event.FlushEvent event)
NHibernate.Impl.SessionImpl.Flush()The difference is that in the NHibernate 5.4.7 case the generated Id is already set on the entity when GetHashCode is called. But in the NHibernate 5.5.0 case the Id is not generated yet. (It is mapped with a sequence generator in my case.)
Was this changed on purpose? If yes, it should be documented (and I have to find a fix for my application 😢)
If not, then please revert the change.