Принуждение 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 ()

(Исключением является то, что объекты, использующие генерацию собственных идентификаторов, вставляются при сохранении.)

Поэтому я могу попросить вас ответить, почему вы добавляете новую сущность с существующим идентификатором? Предполагается, что идентификатор является уникальным для конкретной "сущности". Если эта сущность исчезла, то должен быть и ее идентификатор.

Другим вариантом будет обновление этой записи вместо удаления / вставки. Это сохраняет идентификатор таким образом, чтобы не было уникального нарушения ограничения (по крайней мере, для ключа), и вы можете изменить все остальные данные, чтобы это была "новая" запись.

РЕДАКТИРОВАТЬ: Таким образом, очевидно, я не полностью обращал внимание на вопрос, когда я ответил, так как это проблема с уникальным ограничением на столбец без первичного ключа.

Я думаю, у вас есть два решения на выбор:

  1. Вызов Session.Flush() после вашего удаления, которое выполнит все изменения в сеансе до этого момента, после чего вы можете продолжить с остальными (вставка вашего нового объекта). Это работает и внутри транзакции, поэтому вам не нужно беспокоиться об атомарности.
  2. Создать 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), то я считаю, что он может работать. Это решение предлагается для той же проблемы, возникающей в спящем режиме.

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