Принуждение NHibernate к каскадному удалению перед вставками - PullRequest
22 голосов
/ 01 апреля 2009

У меня есть родительский объект, который имеет отношение один ко многим с 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 ]

30 голосов
/ 02 апреля 2009

Нет возможности указать порядок операций в транзакции, так как она жестко запрограммирована следующим образом (из документации):

Операторы SQL выдаются в следующем порядке

  • все вставки сущностей, в том же порядке соответствующие объекты были сохранены с использованием ISession.Save ()
  • все обновления сущностей
  • все удаления из коллекции
  • все удаления, обновления и вставки всех элементов коллекции
  • все коллекции вставок
  • все удаления сущностей, в том же порядке соответствующие объекты были удалены с помощью ISession.Delete ()

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

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

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

EDIT: Поэтому, очевидно, я не полностью обращал внимание на вопрос, когда отвечал, поскольку это проблема с уникальным ограничением для столбца без первичного ключа.

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

  1. Позвоните Session.Flush() после вашего удаления, которое выполнит все изменения в сеансе до этого момента, после чего вы можете продолжить с остальными (вставка нового объекта). Это также работает внутри транзакции, поэтому вам не нужно беспокоиться об атомарности.
  2. Создание функции ReplacePage, которая обновляет существующую сущность новыми данными, но сохраняет первичный ключ и уникальный столбец одинаковыми.
4 голосов
/ 15 мая 2009

У меня такая же проблема ... У меня есть объект, который имеет коллекцию, которая сопоставлена ​​с таблицей, которая содержит уникальное ограничение.

Что я не понимаю, так это то, что согласно ответу Стюарта, удаление коллекции должно происходить до вставки коллекции? Когда я погружаюсь в исходный код 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, и поскольку для каскада установлено значение Удалить-сирота, объекты, которые должны быть удалены, будут удалены из базы данных.

После того, как я позвоню SaveOrUpdate, я должен обязательно сбросить изменения в базу данных.

Так как каскад удаления-удаления-сироты гарантирует, что никакие OrderLines не будут вставлены или обновлены, я должен перебрать мою коллекцию и вызвать 'SaveOrUpdate' для каждой OrderLine. Это гарантирует, что будут добавлены новые OrderLines, а измененные обновлены. Никакие действия не будут выполнены для OrderLines, которые не изменились.

Хотя это не идеальное решение (это безобразный взлом IMHO), оно вроде работает, и его абстрагируют от хранилища, так что сейчас я так решаю эту проблему ...

0 голосов
/ 03 ноября 2010

Если вы избавитесь от дочернего первичного ключа (ContentPage.Id) и сделаете составной ключ первичным ключом (т. Е. PageNum и ContentID), то я считаю, что он может работать. Это решение предлагается для той же проблемы, возникающей в спящем режиме.

...