8

目的:
親の子のリストへの変更がすべての子に伝播し、NHibernateに手間のかかる作業を行わせるように、親子関係を作成します。親子関係はHas-Many、自己参照テーブル上にあります。

問題:
親(ルート)オブジェクトを削除しようとすると、子オブジェクトを削除するという期待される動作ではなく、例外が発生します。

私が使用しているもののバージョン:
Microsoft SQL
ServerManagementStudioバージョン10.0.4064.0FluentNHibernate
バージョン1.3NHibernateバージョン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つのケースでは発生しません。

  1. コレクションの親が挿入されます
  2. DBから選択された親バージョン
  3. 子供が挿入されました
  4. DBから選択された子バージョン
  5. 親を参照するように子が更新されました(反転なし)
    • -何もありません-子バージョンは選択されていません...

リレーションの更新後の子バージョンが異なるため、DBでインクリメントされたばかりのバージョン...後でStaleExceptionがスローされます。

あなたができる最善のことは、マッピングを拡張して親を持つようにすることです...そしてそれを逆にします

于 2012-12-14T06:04:37.110 に答える