Свободная проблема каскадных соглашений NHibernate - удаленный экземпляр передан для обновления

Обычно у меня все настроено для каскадирования, используя соглашение, вот так:

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();
    }
}

Также используется автоматическое отображение Fluent NHibernate. Обычно он работает очень хорошо, потому что не нужно беспокоиться о конкретных заказах на сохранение и тому подобное. Тем не менее, это оказывается проблемой в следующем сценарии:

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; }
}

Поэтому, когда я сохраняю новый запрос, все, что мне нужно сохранить в методе обслуживания, - это экземпляр запроса, для которого задан набор CompletedQuestionnaire, а в свою очередь - коллекция экземпляров QuestionAnswer. Там нет проблем.

Тем не менее, я также хочу обновить эти ответы на вопросы. В этом случае анкета Inquiry и CompletedQuestion должна оставаться неизменной. Что должно произойти, так это все ответы на вопросы, которые нужно удалить, и создать новые (потому что размер этого списка увеличивается или уменьшается).

Итак, метод обслуживания:

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. Если я пытаюсь изменить каскадирование на Все или UpdateAndSave для IOneToManyCollectionInstance, NHibernate пытается обновить записи QuestionAnswer внешнего ключа CompletedQuestionnaire до нуля, что не удается, поскольку пустые значения не разрешены. Это не будет проблемой, если операторы удаления будут выполняться до этого в соответствии с порядком кода, но странным образом этого не происходит.

Также попытался установить каскад "один-ко-многим" в 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();
}

Теперь я получаю исключение "удаленный объект будет повторно сохранен каскадом (удалить удаленный объект из ассоциаций)". Как правильно удалить объект из ассоциаций?

1 ответ

Whooo! Нашел решение после того, как Чак дал отличное понимание вопроса 302720: как удалить ребенка в объекте nhibernate. Я изменил 2 соглашения так, чтобы:

public void Apply(IOneToManyCollectionInstance instance)
{
    instance.Cascade.AllDeleteOrphan();
    instance.Inverse();
}

public void Apply(IManyToOneInstance instance)
{
    instance.Cascade.SaveUpdate();
}

Теперь это позволяет мне сохранить родителя, и его дочерняя коллекция будет сохранена каскадом. Также это позволяет мне позвонить:

completedQuestionnaire.QuestionAnswers.Clear();

И это удаляет дочерние объекты коллекции.

Другие вопросы по тегам