私は通常、次のような規則を使用して、すべてをカスケードするように設定しています。
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();
}
}
FluentNHibernate自動マッピングも使用します。特定の保存順序などについて心配する必要がないため、通常は非常にうまく機能します。ただし、次のシナリオでは問題になることがわかっています。
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; }
}
したがって、新しいEnquiryを永続化する場合、サービスメソッドに保存する必要があるのは、CompletedQuestionnaireが設定されたInquiryのインスタンスであり、QuestionAnswerインスタンスのコレクションがあります。そこに問題はありません。
ただし、それらの質問の回答も更新したいと思います。この場合、InquiryとCompletedQuestionnaireは同じままである必要があります。発生する必要があるのは、すべての質問の回答を削除し、新しい質問の回答を作成することです(そのリストのサイズが拡大または縮小するため)。
したがって、サービス方法は次のとおりです。
public CompletedQuestionnaire UpdateCompletedQuestionnaire(CompletedQuestionnaire completedQuestionnaire)
{
var oldCompletedQuestionnaire = _completedQuestionnaireRepository.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);
}
これを行うことで得られるのは、「削除されたインスタンスが更新に渡されました」というエラーです。NHibernateソースコードでこの例外の参照を見つけただけです。IOneToManyCollectionInstanceでカスケードをAllまたはUpdateAndSaveに変更しようとすると、NHibernateはQuestionAnswerレコードのCompletedQuestionnaire外部キーをnullに更新しようとしますが、nullは許可されていないため失敗します。コードの順序に従って削除ステートメントがこれより前に実行される場合、これは問題にはなりませんが、奇妙なことにこれは発生しません。
また、1対多のカスケードをDeleteOrphanに設定してみました。これにより、同じ削除されたインスタンスに渡された例外が発生します。
上記のサービス方法は、実際には試行錯誤の結果です。「あなたはそれを間違っている!」についての洞察と説明をいただければ幸いです。
NHiberanteで実行されるステートメントの順序を制御することは可能ですか?DeleteBeforeUpdateなどの規則を言いますか?
編集:私はサービスメソッドを次のように作り直しました:
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;
}
また、関連するカスケード規則を次のように変更しました。
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Cascade.AllDeleteOrphan();
}
「削除されたオブジェクトはカスケードによって再保存されます(削除されたオブジェクトを関連付けから削除します)」という例外が発生します。オブジェクトを関連付けから正しく削除するにはどうすればよいでしょうか。