Коллекция NHibernate Has-Many с каскадным удалением провалилась

Задача:
Создайте отношения родитель-потомок так, чтобы изменения в списке потомков родителя распространялись на всех детей и заставляли NHibernate выполнять тяжелую работу. Отношения родитель-ребенок будут Has-Many на самоссылающейся таблице.

Проблема:
Любая попытка удаления родительского (корневого) объекта вызывает исключения вместо ожидаемого поведения удаления дочерних объектов.

Версии вещей, которые я использую:
Microsoft SQL Server Management Studio Версия 10.0.4064.0
FluentNHibernate Версия 1.3
NHibernate версия 3.2.0.4

Ниже приведен набор объектов текущего класса и структуры таблиц, которые я использую для репликации этого поведения.


// Entity
class Task
{
    ID { get; set; }    
    public virtual IList<Task> Children { get; set; }
    public virtual byte[] Version { get; protected set; }
    public virtual bool IsNew() { return ID <= 0; }

    public Task()
    {
        this.Children = new System.Collections.Generic.List<Task>();
    }
    // Other properties excluded for brevity
}

// Map
class TaskMap : ClassMap<Task>
{
    TaskMap()
    {
        Table("Task");

        Id(x => x.ID, "ID")
            .GeneratedBy.HiLo(
                "NH_HiLo", "NextHigh", "100",
                string.Format("TableName =     '{0}'", "Task"));

        HasMany<Task>(x => x.Children) 
            .KeyColumn("ParentTaskID")
            .Cascade.AllDeleteOrphan();

        // Other properties omitted for brevity

         Version(x => x.Version)
            .Not.Nullable()
            .Generated.Always()
            .Column("Version")
            .CustomSqlType("timestamp");
    }
}

// Repository Delete Method:
public virtual void Delete(Task value)
{
    // CurrentSession is an ISession object that is currently open
    using (var transaction = CurrentSession.BeginTransaction())
    {
        try
        {
            CurrentSession.Delete(value);
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
            throw;
        }
    }
}

// Test Case using NUnit.Framework and FluentNHibernate.Testing:
[TestFixtureSetUp]
public void SetUpFixture()
{
    _repository = new Repository();
}

[Test]
public void MappingTest()
{
    var task = new Task(); // Omitted assigning other properties for brevity

    var entity = new Task(); // Omitted assigning other properties for brevity
    entity.Children.Add(task);

    _entity = new PersistenceSpecification<Task>(_repository.CurrentSession)
        .VerifyTheMappings(entity);
}                       

[TearDown]
public void TearDown()
{
    if (_entity != null && !_entity.IsNew())
    {
        _repository.Delete(_entity);
        _entity = null;
    }
}

--Table Script:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Task](
    [ID] [bigint] NOT NULL,
    [ParentTaskID] [bigint] NULL, -- Notice it DOES HAVE a NULLable FK     reference.
    [Version] [timestamp] NOT NULL,
    CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED(
        [ID] ASC
    ) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF,
        IGNORE_DUP_KEY = OFF,     ALLOW_ROW_LOCKS  = ON,
        ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
ALTER TABLE [dbo].[Task]  WITH CHECK ADD  CONSTRAINT [FK_TasksChild_TasksParent]
    FOREIGN KEY([ParentTaskID])
    REFERENCES [dbo].[Task] ([ID]) -- Notice the self table reference for child objects
GO
ALTER TABLE [dbo].[Task] CHECK CONSTRAINT [FK_TasksChild_TasksParent]
GO

С приведенной выше таблицей и классами, меняя каскад на эти параметры и выполняя указанные во время демонтажа теста, это результаты.


С Cascade.AllDeleteOrphan:
Просто вызывая delete для родительского объекта, я получаю следующее исключение:

NHibernate.StaleObjectStateException was unhandled by user code
Message=Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Task#1015859]
Source=NHibernate
EntityName=Entities.Task
StackTrace:
   at NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32 rows, Object id, Int32 tableNumber, IExpectation expectation, IDbCommand statement) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 2178
   at NHibernate.Persister.Entity.AbstractEntityPersister.Delete(Object id, Object version, Int32 j, Object obj, SqlCommandInfo sql, ISessionImplementor session, Object[] loadedState) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 2912
   at NHibernate.Persister.Entity.AbstractEntityPersister.Delete(Object id, Object version, Object obj, ISessionImplementor session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 3095
   at NHibernate.Action.EntityDeleteAction.Execute() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Action\EntityDeleteAction.cs:line 70
   at NHibernate.Engine.ActionQueue.Execute(IExecutable executable) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs:line 136
   at NHibernate.Engine.ActionQueue.ExecuteActions(IList list) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs:line 126
   at NHibernate.Engine.ActionQueue.ExecuteActions() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs:line 174
   at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs:line 249
   at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\DefaultFlushEventListener.cs:line 19
   at NHibernate.Impl.SessionImpl.Flush() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 1489
   at NHibernate.Transaction.AdoTransaction.Commit() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Transaction\AdoTransaction.cs:line 190
   at Repositories.Repository.Delete(Task value) in ..\Repositories\Repository.cs:line 22
   at TaskTest.TearDown() in ..\Tests\TaskTest.cs:line 76

Итерируя по каждому из детей, рекурсивно копаясь в детях этих детей и пытаясь удалить каждого ребенка снизу вверх:

NHibernate.ObjectDeletedException was unhandled by user code
Message=deleted object would be re-saved by cascade (remove deleted object from associations)[Task#1016061]
Source=NHibernate
EntityName=Entities.Task
StackTrace:
   at NHibernate.Impl.SessionImpl.ForceFlush(EntityEntry entityEntry) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 914
   at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\DefaultSaveOrUpdateEventListener.cs:line 140
   at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\DefaultSaveOrUpdateEventListener.cs:line 76
   at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\DefaultSaveOrUpdateEventListener.cs:line 53
   at NHibernate.Impl.SessionImpl.FireSaveOrUpdate(SaveOrUpdateEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 2662
   at NHibernate.Impl.SessionImpl.SaveOrUpdate(String entityName, Object obj) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 549
   at NHibernate.Engine.CascadingAction.SaveUpdateCascadingAction.Cascade(IEventSource session, Object child, String entityName, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\CascadingAction.cs:line 249
   at NHibernate.Engine.Cascade.CascadeToOne(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs:line 216
   at NHibernate.Engine.Cascade.CascadeAssociation(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs:line 181
   at NHibernate.Engine.Cascade.CascadeProperty(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs:line 148
   at NHibernate.Engine.Cascade.CascadeCollectionElements(Object parent, Object child, CollectionType collectionType, CascadeStyle style, IType elemType, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs:line 240
   at NHibernate.Engine.Cascade.CascadeCollection(Object parent, Object child, CascadeStyle style, Object anything, CollectionType type) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs:line 201
   at NHibernate.Engine.Cascade.CascadeAssociation(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs:line 185
   at NHibernate.Engine.Cascade.CascadeProperty(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs:line 148
   at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object     anything) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs:line 126
   at NHibernate.Event.Default.AbstractFlushingEventListener.CascadeOnFlush(IEventSource session, IEntityPersister persister, Object key, Object anything) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs:line 207
   at NHibernate.Event.Default.AbstractFlushingEventListener.PrepareEntityFlushes(IEventSource session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs:line 197
   at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs:line 48
   at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\DefaultFlushEventListener.cs:line 18
   at NHibernate.Impl.SessionImpl.Flush() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 1489
   at NHibernate.Transaction.AdoTransaction.Commit() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Transaction\AdoTransaction.cs:line 190
   at Repositories.Repository.Delete(Task value) in ..\Repositories\Repository.cs:line 22
   at Repositories.Repository.Delete(Task value) in ..\Repositories\Repository.cs:line 25
   at Repositories.Repository.Delete(Task value) in ..\Repositories\Repository.cs:line 25
   at Tests.TaskTest.TearDown() in ..\Tests\TaskTest.cs:line 76

После перебора дочерних элементов, очистки каждого из их наборов дочерних элементов, а затем сохранения / удаления родительского элемента я получаю следующее исключение:

NHibernate.StaleObjectStateException was unhandled by user code
Message=Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Task#1015960]
Source=NHibernate
EntityName=Entities.Task
StackTrace:
   at NHibernate.Persister.Entity.AbstractEntityPersister.Check(Int32 rows, Object id, Int32 tableNumber, IExpectation expectation, IDbCommand statement) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 2178
   at NHibernate.Persister.Entity.AbstractEntityPersister.Delete(Object id, Object version, Int32 j, Object obj, SqlCommandInfo sql, ISessionImplementor session, Object[] loadedState) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 2912
   at NHibernate.Persister.Entity.AbstractEntityPersister.Delete(Object id, Object version, Object obj, ISessionImplementor session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 3095
   at NHibernate.Action.EntityDeleteAction.Execute() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Action\EntityDeleteAction.cs:line 70
   at NHibernate.Engine.ActionQueue.Execute(IExecutable executable) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs:line 136
   at NHibernate.Engine.ActionQueue.ExecuteActions(IList list) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs:line 126
   at NHibernate.Engine.ActionQueue.ExecuteActions() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\ActionQueue.cs:line 174
   at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs:line 249
   at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\DefaultFlushEventListener.cs:line 19
   at NHibernate.Impl.SessionImpl.Flush() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 1489
   at NHibernate.Transaction.AdoTransaction.Commit() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Transaction\AdoTransaction.cs:line 190
   at Repositories.Repository.Delete(Task value) in ..\Repositories\Repository.cs:line 22
   at TaskTest.TearDown() in ..\Tests\TaskTest.cs:line 76

С Cascade.All, просто вызывая delete для родительского объекта, я получаю следующее исключение:
То же, что и для Cascade.AllDeleteOrphan

Итерируя по каждому из детей, рекурсивно копаясь в детях этих детей и пытаясь удалить каждого ребенка снизу вверх:
То же, что и для Cascade.AllDeleteOrphan

После перебора дочерних элементов, очистки каждого из их наборов дочерних элементов, а затем сохранения / удаления родительского элемента я получаю следующее исключение:
Не исключение: родитель удаляется правильно, но теперь у меня есть потерянные объекты, которые я не хочу!


Я просмотрел много блогов / вопросов о стековом потоке / документации по ресурсам и не нашел решения этой проблемы.
Вот несколько ссылок, которые я уже выкопал:


Во многих постах упоминается переворачивание отношений, но установка.inverse и создание отношений между ребенком - совершенно не цель!

Я понятия не имею, что мне не хватает, но, надеюсь, это что-то действительно простое, чтобы исправить, что я упускаю из виду. Любая помощь будет высоко ценится!

1 ответ

Решение

Вы ничего не пропускаете. Это комбинация вашего сопоставления: а) дочерний элемент не имеет сопоставленного родительского элемента, б) дочерний элемент версионирован, в) коллекция не установлена ​​как обратная (поскольку она не может управляться дочерним элементом без сопоставленного родительского элемента) г) и, наконец, большинство скорее всего из-за ошибки.

Что происходит, так это то, что при управлении версиями любой оператор INSERT или UPDATE сопровождается SELECT..., чтобы получить последнюю версию timestamp генерируется сервером БД. Но это не происходит в одном случае:

  1. Родитель коллекции вставлен
  2. родительская версия выбрана из БД
  3. ребенок вставлен
  4. дочерняя версия выбрана из БД
  5. дочерний элемент обновлен (без инверсии) для ссылки на родителя
    • - НИЧЕГО - дочерняя версия НЕ выбрана...

Поскольку дочерняя версия после обновления отношения отличается от версии, только что увеличенной в БД... позже выдается исключение StaleException.

Лучшее, что вы можете сделать, это расширить отображение, чтобы иметь Родителя... и сделать его обратным

Другие вопросы по тегам