Принуждение 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 выполнить сначала удаление?
Ответы
Ответ 1
Невозможно указать порядок операций в транзакции, поскольку он жестко закодирован следующим образом (из документации):
Операторы SQL выдаются в следующем порядке
- все вставки объектов, в том же порядке соответствующие объекты были сохранены с использованием ISession.Save()
- все обновления сущностей
- удаление всех коллекций
- удаление, обновление и вставка элементов коллекции
- все вставки коллекции
- все удаления сущностей, в том же порядке соответствующие объекты были удалены с использованием ISession.Delete()
(Исключением является то, что объекты, использующие встроенную идентификацию, вставляются, когда они сохраняются.)
Как таковой, могу ли я дать вам ответ на вопрос, почему вы добавляете новый объект с существующим идентификатором? Идентификатор должен быть уникальным для определенного "объекта". Если этот объект отсутствует, то должен быть его идентификатор.
Другой вариант - сделать обновление для этой записи вместо удаления/вставки. Это делает идентификатор одинаковым, поэтому нет уникального нарушения ограничений (по крайней мере, по ключу), и вы можете изменить все остальные данные так, чтобы это была "новая" запись.
EDIT:
Поэтому, по-видимому, я не совсем обратил внимание на вопрос, когда я ответил, поскольку это проблема с уникальным ограничением на столбце с непервичными ключами.
Думаю, у вас есть два решения на выбор:
- Вызовите
Session.Flush()
после удаления, которое выполнит все изменения в сеансе до этой точки, после чего вы сможете продолжить с остальными (вставляя новый объект). Это также работает внутри транзакции, поэтому вам не нужно беспокоиться об атомарности.
- Создайте функцию
ReplacePage
, которая обновляет существующий объект новыми данными, но сохраняет первичный ключ и уникальный столбец одинаковыми.
Ответ 2
У меня такая же проблема...
У меня есть объект, который имеет коллекцию, которая сопоставляется с таблицей, которая содержит уникальное ограничение.
То, что я не понимаю, заключается в том, что, согласно ответу Стюарта, удаление коллекции должно происходить до вставки коллекции?
Когда я погружаюсь в исходный код NHibernate, я нахожу класс CollectionUpdateAction
, который содержит этот код в методе Execute
:
persister.DeleteRows(collection, id, session);
persister.UpdateRows(collection, id, session);
persister.InsertRows(collection, id, session);
Тогда я бы предположил, что удаления выполняются перед вставками, но, видимо, это не так.
Не используется ли CollectionUpdateAction в этом сценарии? Когда используется CollectionUpdateAction?
Ответ:
Я работал над этим следующим образом:
- В моем сопоставлении я установил параметр каскада в "delete-orphan" вместо "all-delete-orphan"
-
Весь доступ к моей базе данных осуществляется через репозиторий; в методе сохранения моего репозитория у меня есть этот код для моей сущности:
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, которые не изменились.
Хотя это не идеальное решение (это уродливое взломать IMHO), оно вроде как работает, и оно абстрагируется за репозиторием, так вот как я справляюсь с этой проблемой на данный момент...
Ответ 3
Если вы избавитесь от дочернего первичного ключа (ContentPage.Id) и сделайте составной ключ основным ключом (т.е. PageNum и ContentID), то я считаю, что он может работать. Это решение, предлагаемое по той же проблеме, что и в спящем режиме.