8

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

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

4

1 回答 1

5

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

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

  1. 集合父级已插入
  2. 从数据库中选择的父版本
  3. 插入的孩子
  4. 从数据库中选择的子版本
  5. 子更新(无反转)以引用父
    • -- 无 - 未选择子版本...

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

您能做的最好的事情是将映射扩展为有一个 Parent... 并使其反转

于 2012-12-14T06:04:37.110 回答