目的:
親の子のリストへの変更がすべての子に伝播し、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の質問/リソースドキュメントを調べましたが、この問題の解決策は実際には見ていません。
これが私がすでに掘り下げたリンクのいくつかです:
- FluentNHibernateを使用して参照オブジェクトを削除する方法(古い「削除されたオブジェクトはカスケードによって再保存されます」)
- カスケードのエラー:削除されたオブジェクトはカスケードによって再保存されます
- オートマッパーを使用したカスケード削除によるFluentNHibernateの1対多のセットアップ
- key-many-to-oneおよびkey-propertyの関連付け:nhibernateはセットからアイテムを削除しません
- https://nhibernate.jira.com/browse/NH-1050
(ヌルのFKがあることに注意してください) - 行が別のトランザクションによって更新または削除された(または未保存の値のマッピングが正しくなかった)
(タイムスタンプの楽観的なバージョン管理を使用していることに注意してください) - https://forum.hibernate.org/viewtopic.php?t=933496(cascade=
allに関するernst_pluessによるコメントは、HiLoの使用に関する警告フラグをスローしましたが、HiLoは生成または割り当てられていますか?技術的にはNHibernateが生成してから割り当てます。 。) - NHibernateでの子レコードの削除の例外
(すべての子オブジェクトを手動で削除する必要があるように見えます。これにより、カスケードの目的が失われます!!)
投稿の多くは関係を逆転させることに言及していますが、.inverseを設定し、子供に関係を所有させることは、ここでの目標ではありません!
何が欠けているのかわかりませんが、うまくいけば、これは私が見落としている修正が本当に簡単なものです。どんな助けでも大歓迎です!