带有级联删除的NHibernate Has-Many集合失败

时间:2012-12-13 22:52:46

标签: c# nhibernate orm fluent-nhibernate nhibernate-cascade

目标:
创建父子关系,以便修改父项的子项列表将传播给所有子项,并让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

相同

在遍历孩子后,清除他们的每个孩子组,然后保存/删除父母我得到这个例外:
没有例外:父项被正确删除但现在我有了我不想要的孤立对象!


我查看了许多博客/ stackoverflow问题/资源文档,并没有真正看到这个问题的解决方案。
以下是我已经挖过的一些链接:


许多帖子提到反转关系,但设置.inverse并让孩子拥有这段关系完全不是这里的目标!

我不知道我错过了什么,但希望这是一个非常简单的解决方法,我忽略了。任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:5)

遗漏任何东西。它是您的映射的组合:a)子级没有父映射,b)子级版本化,c)集合未设置为反向(因为它不能由没有映射父级的子级管理)d)最后,大多数可能是由于一个错误。

发生的情况是,对于版本控制,任何INSERT或UPDATE语句后跟SELECT ...以获取DB Server生成的最新timestamp。但在一种情况下不会发生这种情况:

  1. 插入了集合父级
  2. 从DB
  3. 中选择的父版本
  4. 插入儿童
  5. 从DB
  6. 中选择的子版本
  7. 子项更新(无反转)以引用父项
    • - NOTHING - 未选择子版本...
  8. 因为关系更新后的子版本不同,所以在DB中递增的版本...稍后抛出StaleException。

    您可以做的最好的事情是将映射扩展为具有父级...并使其反转