2012-12-13 9 views
6

Ziel:
Erstellen Sie eine Eltern-Kind-Beziehung, so dass Änderungen an der Elternliste von Kindern an alle Kinder weitergeben und NHibernate das schwere Heben haben. Die Eltern-Kind-Beziehung ist eine Has-Many in einer sich selbst referenzierenden Tabelle.NHibernate Has-Many Collection mit Cascading Deletes schlägt fehl

Problem:
Jeder Versuch, das übergeordnete Objekt (das Stammobjekt) zu löschen, verursacht Ausnahmen anstelle des erwarteten Verhaltens beim Löschen der untergeordneten Objekte.

Versionen von Sachen, die ich bin mit:
Microsoft SQL Server Management Studio Version 10.0.4064.0
FluentNHibernate Version 1.3
NHibernate Version 3.2.0.4

Unten finden Sie die Menge der aktuellen Klasse Objekte und Tabellenstruktur I verwende um dieses Verhalten zu replizieren.


// 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 

Mit den obigen Tabelle und Klassen, die Änderung der Kaskade zu diesen Optionen und Ausführen der angegebenen während der Teardown des Tests, sind dies die Ergebnisse.


Mit Cascade.AllDeleteOrphan:
einfach auf das übergeordnete Objekt löschen Aufruf erhalte ich diese Ausnahme:

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 

Nachdem durch jedes der Kinder laufen, rekursiv durch jene Kinder, Kinder Graben und versuchen, jedes Kind von unten nach oben zu löschen:

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 

Nachdem durch die Kinder laufen, jeder ihrer Sätze von Kindern löschen, dann Speichern/Löschen der Eltern bekomme ich diese Ausnahme:

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 

Mit Cascade.All, einfach auf der anruf löschen Übergeordnetes Objekt Ich bekomme diese Ausnahme:
Wie für Cascade.AllDeleteOrphan

Nach dem Iterieren durch jedes der Kinder rekursiv durch die Kinder dieser Kinder graben und versuchen zu löschen te jedes Kind von unten nach oben:
Wie bei Cascade.AllDeleteOrphan

Nachdem durch die Kinder laufen, jeder ihrer Sätze von Kindern löschen, dann Speichern/Löschen der Eltern bekomme ich diese Ausnahme:
keine Ausnahme: Die Eltern richtig gelöscht wird, aber jetzt habe ich Objekte verwaist, die ich nicht tun wollen!


Ich habe das sah durch viele Blogs/Stackoverflow Fragen/Ressource-Dokumentation und haben nicht wirklich eine Lösung für dieses Problem zu sehen.
Hier einige der Links, die ich durch bereits gegraben haben:


wegnimmt

Viele der Beiträge erwähnen Umkehrung der Beziehung, aber die Einstellung .inverse und das Kind die Beziehung zu besitzen, ist hier nicht das Ziel!

Ich habe keine Ahnung, was ich vermisse, aber hoffentlich ist das etwas wirklich einfaches zu beheben, das ich übersehe. Jede Hilfe wird sehr geschätzt!

Antwort

5

Sie sind nicht fehlt etwas. Es ist eine Kombination aus Ihrem Mapping: a) Kind hat kein Parent zugeordnet, b) Kind ist versioniert, c) Sammlung ist nicht als invers eingestellt (weil es nicht von Kind ohne zugeordnetes Elternteil verwaltet werden kann) d) und schließlich am meisten wahrscheinlich aufgrund eines Fehlers.

Was passiert, ist, dass mit Versionierung jede INSERT oder UPDATE-Anweisung von SELECT ... gefolgt wird, um die neueste timestamp generiert von DB Server zu erhalten. Aber dies geschieht nicht in einem Fall:

  1. Sammlung Mutter
  2. Parent-Version von DB ausgewählt eingeführt wird
  3. Kind eingeführt
  4. Kind Version von DB ausgewählt
  5. Kind aktualisiert (keine Inversion) zum Bezugselternteil
    • - NICHTS - ch Die Version ist NICHT ausgewählt ...

Weil das Kind Version nach der Beziehung Update unterscheidet sich dann derjenige gerade in DB erhöht ... später die StaleException geworfen wird.

Das Beste, was Sie tun können, ist die Zuordnung zu verlängern, um einen Elternteil zu haben ... und es

machen inverse
+0

ich Ihre Antwort zu schätzen wissen. Das war nicht genau das, was ich zu hören hoffte, obwohl ich hoffte, dass ich die Beziehung zwischen Eltern und Kind nicht manuell brechen musste, aber es scheint, dass es unvermeidlich ist. Ich habe den Test bestanden, indem ich eine ParentTask-Referenz hinzufügte und die delete-Methode anpasste, um rekursiv in den Child-Listen nach unten zu graben, die Eltern auf null zu setzen, um die Krawatte zu brechen und schließlich die Eltern-Task zu löschen, die korrekt ist, wenn Cascade.AllDeleteOrphan eingeschaltet ist Löscht alle verwaisten Aufgaben. Nochmals vielen Dank für Ihre Antwort. –