Closed
Description
When trying to delete an orphaned child entity in a one-to-one
association, an InvalidCastException
is thrown if session.Flush()
is called explicitly.
Example (full solution at https://github.com/nkreipke/NHOneToOneBug):
<class name="Entity1">
<id name="Id">
<generator class="guid"/>
</id>
<one-to-one name="Child" cascade="all-delete-orphan" constrained="true" />
</class>
<class name="Entity2">
<id name="Id">
<generator class="foreign">
<param name="property">Parent</param>
</generator>
</id>
<one-to-one name="Parent" />
</class>
var entity = session.Query<Entity1>().First(x => x.Id == id);
// Child entity should be deleted due to all-delete-orphan cascading
entity.Child = null;
session.Update(entity);
// !!! This is required for the bug to trigger
session.Flush();
// !!! InvalidCastException
transaction.Commit();
This will throw the following exception at transaction.Commit()
:
System.InvalidCastException: Unable to cast object of type 'NHOneToOneBug.Entity2' to type 'NHOneToOneBug.Entity1'.
at (Object )
at NHibernate.Bytecode.Lightweight.AccessOptimizer.GetSpecializedPropertyValue(Object target)
at NHibernate.Bytecode.AccessOptimizerExtensions.GetSpecializedPropertyValue(IAccessOptimizer optimizer, Object target)
at NHibernate.Tuple.Entity.PocoEntityTuplizer.GetIdentifierPropertyValue(Object entity)
at NHibernate.Tuple.Entity.AbstractEntityTuplizer.GetIdentifier(Object entity)
at NHibernate.Persister.Entity.AbstractEntityPersister.GetIdentifier(Object obj)
at NHibernate.Persister.Entity.AbstractEntityPersister.IsTransient(Object entity, ISessionImplementor session)
at NHibernate.Engine.ForeignKeys.IsTransientFast(String entityName, Object entity, ISessionImplementor session)
at NHibernate.Engine.ForeignKeys.IsTransientSlow(String entityName, Object entity, ISessionImplementor session)
at NHibernate.Event.Default.DefaultDeleteEventListener.OnDelete(DeleteEvent event, ISet`1 transientEntities)
at NHibernate.Impl.SessionImpl.FireDelete(DeleteEvent event, ISet`1 transientEntities)
at NHibernate.Impl.SessionImpl.Delete(String entityName, Object child, Boolean isCascadeDeleteEnabled, ISet`1 transientEntities)
at NHibernate.Engine.Cascade.CascadeProperty(Object parent, Object child, IType type, CascadeStyle style, String propertyName, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything)
at NHibernate.Event.Default.AbstractFlushingEventListener.CascadeOnFlush(IEventSource session, IEntityPersister persister, Object key, Object anything)
at NHibernate.Event.Default.AbstractFlushingEventListener.PrepareEntityFlushes(IEventSource session)
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Impl.SessionImpl.FlushBeforeTransactionCompletion()
at NHibernate.Impl.SessionImpl.BeforeTransactionCompletion(ITransaction tx)
at NHibernate.Transaction.AdoTransaction.Commit()
This only happens if session.Flush()
was called beforehand. Without explicit flushing, the child entity is deleted correctly.
I suspect the bug is in SessionImpl
. In this line, the values of entityName
(which is Entity1
) and child
(which is of type Entity2
) are copied into the DeleteEvent
fields entityName
and entity
, and the code might later assume they refer to the same type:
Metadata
Metadata
Assignees
Labels
No labels