Подтвердить что ты не робот

Принуждение 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 выполнить сначала удаление?

4b9b3361

Ответ 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), то я считаю, что он может работать. Это решение, предлагаемое по той же проблеме, что и в спящем режиме.