Skip to content

Cascade all-delete-orphan of constrained one-to-one association throws InvalidCastException on explicit flush #3403

Closed
@nkreipke

Description

@nkreipke

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:

FireDelete(new DeleteEvent(entityName, child, isCascadeDeleteEnabled, this), transientEntities);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions