2012-12-13 11 views
6

Cel:
Stwórz relację rodzic-dziecko, tak aby modyfikacje listy dzieci nadrzędnych rozprzestrzeniły się na wszystkie dzieci i mają NHibernate do ciężkiego podnoszenia. relacji rodzic-dziecko będzie Has-Many na stole własnym odsyłania.NHibernate Has-Many Kolekcja z usuwaniem kaskadowym kończy się niepowodzeniem

Problem:
Każda próba usunięcia obiektu nadrzędnego (głównego) powoduje wyjątki zamiast oczekiwanego zachowania obiektów potomnych.

Wersje rzeczy używam:
Microsoft SQL Server Management Studio Wersja 10.0.4064.0
FluentNhibernate Wersja 1.3
NHibernate wersja 3.2.0.4

Poniżej znajduje się zestaw aktualnych klasy obiektów i struktury tabeli I używam do replikowania tego zachowania.


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

W powyższej tabeli i klasach, zmiana kaskady na te opcje i wykonanie określonej podczas rozłączenia testu, są to wyniki.


Z Cascade.AllDeleteOrphan:
prostu dzwoniąc usunąć z obiektu nadrzędnego otrzymuję ten wyjątek:

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 

Po iteracja każdego z dzieci, rekurencyjnie kopanie przez dzieci tych dzieci i próba usunięcia każdego dziecka od dołu:

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 

Po iteracja dzieci, wyczyszczenie każdego ze swoich zbiorów dzieci, a następnie zapisywanie/usuwanie rodzica otrzymuję ten wyjątek:

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 

Z Cascade.All, po prostu dzwoniąc na usunięcie obiekt nadrzędny otrzymuję ten wyjątek:
samo jak dla Cascade.AllDeleteOrphan

Po iteracja każdego z dzieci, rekurencyjnie kopanie przez dzieci tych dzieci i próbuje dele Te każde dziecko od dołu do góry:
samo jak dla Cascade.AllDeleteOrphan

Po iteracja dzieci, wyczyszczenie każdego ze swoich zbiorów dzieci, a następnie zapisywanie/usuwanie rodzica otrzymuję ten wyjątek:
bez wyjątku: Rodzic zostanie usunięty poprawnie, ale teraz mam osierocony przedmiotów, że nie chcieć!


Mam przejrzałem wiele blogów/stackoverflow pytania/dokumenty zasobów i naprawdę nie widziałem rozwiązania tego problemu.
Oto kilka linków Mam wykopanych przez już:


Wiele postów wspomina o odwracaniu związku, ale ustawienie. Odwrotność i sprawienie, że dziecko jest właścicielem związku, nie jest tutaj celem!

Nie mam pojęcia, czego mi brakuje, ale mam nadzieję, że jest to coś naprawdę prostego do naprawienia, które przeoczyłem. Każda pomoc będzie doceniona!

Odpowiedz

5

Nie jesteś ani niczego nie brakuje. Jest to kombinacja twojego mapowania: a) dziecko nie ma przypisanego Rodzica, b) dziecko jest wersjonowane, c) kolekcja nie jest ustawiona jako odwrotna (ponieważ nie może być zarządzana przez dziecko bez zmapowanego rodzica) d) i na koniec, większość prawdopodobnie z powodu błędu.

Co się dzieje z wersjonowaniem, po instrukcji INSERT lub UPDATE następuje SELECT ..., aby uzyskać najnowszą timestamp wygenerowaną przez serwer DB. Ale to nie zdarza się w jednym przypadku:

  1. Collection rodzic jest włożona
  2. wersję nadrzędnego wybrany z DB
  3. dziecko włożona wersja
  4. dziecko wybrane z DB
  5. dziecko aktualizowane (bez inwersji) w celu odniesienia do rodzica
    • - NIC - ch Wersja ild NIE JEST wybrana ...

Ponieważ wersja dziecko po aktualizacji relacja różni się wtedy tylko jeden zwiększany w DB ... później StaleException jest wyrzucane.

Najlepsze co możesz zrobić, to rozszerzyć mapowanie, aby mieć rodzica ... i uczynić go odwrotnym

+0

Doceniam twoją odpowiedź. Nie było to dokładnie to, co miałem nadzieję usłyszeć, ponieważ miałem nadzieję, że nie muszę ręcznie zerwać związku między rodzicem a dzieckiem, ale wygląda na to, że jest nieuniknione. Otrzymałem test do przekazania, dodając odwołanie do ParentTask i ulepszając metodę delete, aby rekursywnie przeglądać listy potomne ustawiając parent na wartość null, aby zerwać remis i ostatecznie usunąć zadanie nadrzędne, które po włączeniu Cascade.AllDeleteOrphan jest prawidłowe usuwa wszystkie osierocone zadania. Jeszcze raz, dziękuję za odpowiedź. –

Powiązane problemy