Принуждение NHibernate к каскадному удалению перед вставками
У меня есть родительский объект, который имеет отношение один ко многим с ISet
дочерних объектов. Дочерние объекты имеют уникальное ограничение (PageNum
а также ContentID
- внешний ключ к родителю).
<set name="Pages" inverse="true" cascade="all-delete-orphan" access="field.camelcase-underscore">
<key column="ContentId" />
<one-to-many class="DeveloperFusion.Domain.Entities.ContentPage, DeveloperFusion.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</set>
Проблема, с которой я столкнулся, заключается в том, ContentPage
элемент из родительской коллекции, а затем добавить новый с тем же уникальным ключом в той же транзакции... Вы получаете уникальное нарушение ограничения, потому что NHibernate пытается выполнить вставку до удаления.
Есть ли способ заставить NHibernate сначала выполнить удаление?
3 ответа
Нет возможности указать порядок операций в транзакции, так как она жестко запрограммирована следующим образом (из документации):
Операторы SQL выдаются в следующем порядке
- все вставки сущностей, в том же порядке соответствующие объекты были сохранены с помощью ISession.Save()
- все обновления сущностей
- все удаления коллекции
- все удаления элементов коллекции, обновления и вставки
- все коллекции вставок
- все удаления сущностей, в том же порядке соответствующие объекты были удалены с помощью ISession.Delete ()
(Исключением является то, что объекты, использующие генерацию собственных идентификаторов, вставляются при сохранении.)
Поэтому я могу попросить вас ответить, почему вы добавляете новую сущность с существующим идентификатором? Предполагается, что идентификатор является уникальным для конкретной "сущности". Если эта сущность исчезла, то должен быть и ее идентификатор.
Другим вариантом будет обновление этой записи вместо удаления / вставки. Это сохраняет идентификатор таким образом, чтобы не было уникального нарушения ограничения (по крайней мере, для ключа), и вы можете изменить все остальные данные, чтобы это была "новая" запись.
РЕДАКТИРОВАТЬ: Таким образом, очевидно, я не полностью обращал внимание на вопрос, когда я ответил, так как это проблема с уникальным ограничением на столбец без первичного ключа.
Я думаю, у вас есть два решения на выбор:
- Вызов
Session.Flush()
после вашего удаления, которое выполнит все изменения в сеансе до этого момента, после чего вы можете продолжить с остальными (вставка вашего нового объекта). Это работает и внутри транзакции, поэтому вам не нужно беспокоиться об атомарности. - Создать
ReplacePage
функция, которая обновляет существующую сущность новыми данными, но сохраняет первичный ключ и уникальный столбец одинаковыми.
Я испытываю ту же проблему... У меня есть объект, коллекция которого сопоставлена с таблицей, содержащей уникальное ограничение.
Что я не понимаю, так это то, что согласно ответу Стюартса, удаление коллекции должно происходить до вставки коллекции? Когда я погружаюсь в исходный код NHibernate, я нахожу CollectionUpdateAction
класс, который содержит этот код в своем Execute
метод:
persister.DeleteRows(collection, id, session);
persister.UpdateRows(collection, id, session);
persister.InsertRows(collection, id, session);
Тогда я бы предположил, что удаление выполняется перед вставкой, но, видимо, это не так. В этом сценарии не используется действие CollectionUpdateAction? Когда используется действие CollectionUpdateAction?
Ответ:
Я работал над этим так:
- В моем отображении я установил опцию каскада "удалить-сироту" вместо "все-удалить-сироту"
Весь мой доступ к базе данных идет через репозиторий; в методе сохранения моего репозитория у меня есть этот код для моей сущности:
public void Save( Order orderObj ) { // Only starts a transaction when there is no transaction // associated yet with the session With.Transaction(session, delegate() { session.SaveOrUpdate (orderObj); session.Flush(); foreach( OrderLine line in orderObj.Lines ) { session.SaveOrUpdate (line); } }; }
Итак, я сохраняю orderObj, и поскольку для каскада задано значение delete-orphan, объекты, которые должны быть удалены, будут удалены из базы данных.
После того как я позвоню SaveOrUpdate
Я должен убедиться, что сбросил изменения в базу данных.
Поскольку каскадный параметр удаления-сироты гарантирует, что никакие OrderLines не будут вставлены или обновлены, я должен перебрать мою коллекцию и вызвать 'SaveOrUpdate' для каждой OrderLine. Это гарантирует, что будут добавлены новые OrderLines, а измененные обновлены. Никакие действия не будут выполнены для OrderLines, которые не изменились.
Хотя это не идеальное решение (это безобразный взлом ИМХО), оно вроде работает, и его абстрагируют от хранилища, так что вот как я сейчас решаю эту проблему...
Если вы избавитесь от дочернего первичного ключа (ContentPage.Id) и сделаете составной ключ первичным ключом (т.е. PageNum и ContentID), то я считаю, что он может работать. Это решение предлагается для той же проблемы, возникающей в спящем режиме.