【问题标题】:NHibernate Has-Many Collection With Cascading Deletes is FailingNHibernate 具有级联删除的多集合失败
【发布时间】:2012-12-13 22:52:45
【问题描述】:

目标:
创建一个父子关系,这样对父子列表的修改将传播到所有子节点,并让 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 并让孩子拥有这种关系完全不是这里的目标!

我不知道我错过了什么,但希望这是我忽略的非常容易解决的问题。任何帮助将不胜感激!

【问题讨论】:

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


    【解决方案1】:

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

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

    1. 已插入集合父项
    2. 从 DB 中选择的父版本
    3. 子插入
    4. 从 DB 中选择子版本
    5. 子代已更新(无反转)以引用父代
      • -- NOTHING - 未选择子版本...

    因为关系更新后的子版本与 DB 中刚刚增加的版本不同...稍后会抛出 StaleException。

    你能做的最好的就是扩展映射以拥有一个 Parent... 并使其反转

    【讨论】:

    • 感谢您的回复。这并不是我希望听到的,因为我希望我不必手动破坏父母和孩子之间的关系,但看起来这是不可避免的。我确实通过添加 ParentTask 引用并调整删除方法以递归地挖掘子列表,将父级设置为 null 以打破平局并最终删除父任务,当 Cascade.AllDeleteOrphan 正确打开时,我确实通过了测试删除所有孤立的任务。再次感谢您的回复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 2011-10-12
    相关资源
    最近更新 更多