Fluent NHibernate cascading conventions issue - deleted instance passed to update
I normally have everything set to cascade all by using a convention, like so:
public class CascadeAllConvention : IHasOneConvention, IHasManyConvention, IReferenceConvention
{
public void Apply(IOneToOneInstance instance)
{
instance.Cascade.All();
}
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Cascade.All();
}
public void Apply(IManyToOneInstance instance)
{
instance.Cascade.All();
}
}
Also using Fluent NHibernate auto mapping. It normally works very well because one doesn't have to worry about specific save orders and alike. However, it's proving to be an issue in the following scenario:
public class QuestionAnswer
{
public CompletedQuestionnaire CompletedQuestionnaire { get; set; }
}
public class CompletedQuestionnaire
{
public long CompletedQuestionnaireId { get; set; }
public IEnumerable<QuestionAnswer> { get; set; }
}
public class Enquiry
{
EnquiryId { get; set; }
CompletedQuestionnaire { get; set; }
}
So, when I'm persisting a new Enquiry, all I have to save in service method is an instance of Enquiry which has CompletedQuestionnaire set and that in turn has a collection of QuestionAnswer instances. No problems there.
However, I also want to update those question answers. In this case the Enquiry and CompletedQuestionnaire need to remain the same. What needs to happen is all question answers to be deleted and new ones created (because the size of that list grows or shrinks).
So the service method is:
public CompletedQuestionnaire UpdateCompletedQuestionnaire(CompletedQuestionnaire completedQuestionnaire)
{
var oldCompletedQuestionnaire = _c开发者_如何转开发ompletedQuestionnaireRepository.Single(q => q.CompletedQuestionnaireId == completedQuestionnaire.CompletedQuestionnaireId);
Guard.AgainstEntityLoadException(oldCompletedQuestionnaire, completedQuestionnaire.CompletedQuestionnaireId);
foreach (var oldQuestionAnswer in oldCompletedQuestionnaire.QuestionAnswers)
_questionAnswerRepository.Delete(oldQuestionAnswer);
var answerCount = oldCompletedQuestionnaire.QuestionAnswers.Count();
for (var index = 0; index < answerCount; index++ )
((IList<QuestionAnswer>) oldCompletedQuestionnaire.QuestionAnswers).RemoveAt(0);
_completedQuestionnaireRepository.Update(oldCompletedQuestionnaire);
foreach (var newQuestionAnswer in completedQuestionnaire.QuestionAnswers)
{
newQuestionAnswer.CompletedQuestionnaire = oldCompletedQuestionnaire;
_questionAnswerRepository.Add(newQuestionAnswer);
}
UnitOfWork.Commit();
return _completedQuestionnaireRepository.Single(q => q.CompletedQuestionnaireId == completedQuestionnaire.CompletedQuestionnaireId);
}
What I get from doing this is an error "deleted instance passed to update". I only found a reference this exception in NHibernate source code. If I try to change cascading to All or UpdateAndSave on IOneToManyCollectionInstance, NHibernate tries to update QuestionAnswer records CompletedQuestionnaire foreign key to null which fails because nulls are not allowed. This would not be a problem if the delete statements would run prior to this as per code order but strangely this does not happen.
Also tried setting cascade on one to many to DeleteOrphan which gives same deleted instance passed exception.
The service method above is actually a result of trial and error. I would appreciate any insight and explanation for "You're doing it wrong!".
Is it possible to control order of statements executed with NHiberante. Say a convention like DeleteBeforeUpdate or similar?
EDIT: I've reworked the service method to:
public CompletedQuestionnaire UpdateCompletedQuestionnaire(long completedQuestionnaireId, IEnumerable<QuestionAnswer> newAnswers)
{
var completedQuestionnaire = _completedQuestionnaireRepository.Single(q => q.CompletedQuestionnaireId == completedQuestionnaireId);
Guard.AgainstEntityLoadException(completedQuestionnaire, completedQuestionnaireId);
((IList<QuestionAnswer>)completedQuestionnaire.QuestionAnswers).Clear();
foreach (var newAnswer in newAnswers)
{
newAnswer.CompletedQuestionnaire = completedQuestionnaire;
_questionAnswerRepository.Add(newAnswer);
}
_completedQuestionnaireRepository.Update(completedQuestionnaire);
UnitOfWork.Commit();
return completedQuestionnaire;
}
And changed related cascade convention to:
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Cascade.AllDeleteOrphan();
}
Now I get exception "deleted object would be re-saved by cascade (remove deleted object from associations)". How would one go about to remove the object from associations correctly?
Whooo! Found a solution after Chuck provided excellent insight on question 302720: how-to-delete-child-object-in-nhibernate. I've changed 2 conventions so that:
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Cascade.AllDeleteOrphan();
instance.Inverse();
}
public void Apply(IManyToOneInstance instance)
{
instance.Cascade.SaveUpdate();
}
This now allows me to save a parent and it's child collection will be saved by cascade. Also it allows me to call:
completedQuestionnaire.QuestionAnswers.Clear();
And it deletes child collection entities.
精彩评论