Двунаправленное поведение каскадного удаления один-ко-многим (или многие-к-одному).Это работает, но почему? - PullRequest
2 голосов
/ 18 марта 2011

У меня есть два отображения Nhibernate для двух классов: Категория и Продукт.Мой класс Category имеет два свойства, которые являются коллекциями.Свойство Children представляет собой коллекцию типа Category, которая представляет дочерние категории (представляет меню категорий, типичный сценарий родительских дочерних элементов).Второе свойство в классе Category - это коллекция Products, в которой представлены все товары в категории.

Я пытаюсь добиться того, чтобы при удалении категории я хотел удалить категорию, но не продукт.Поэтому я хочу, чтобы продукт был осиротевшим.т.е. его внешний ключ (CategoryId) в таблице Product установлен на нуль.Я не хочу удалять продукт только потому, что удалил категорию.Я хочу иметь возможность позже переназначить другую категорию.Мои сопоставления, представляющие упомянутый сценарий, приведены ниже.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="naakud.domain" namespace="naakud.domain">
  <class name="Category">
    <id name="Id">
      <generator class="hilo" />
    </id>
    <version name="Version"/>
    <property name="Name" not-null="true" unique="true" />
    <set name="Products"
         cascade="save-update"
         inverse="true"
         access="field.camelcase-underscore">
      <key column="CategoryId" foreign-key="fk_Category_Product" />
      <one-to-many class="Product" />
    </set>
    <many-to-one name="Parent" class="Category" column="ParentId" />
    <set name="Children"
         collection-type="naakud.domain.Mappings.Collections.TreeCategoriesCollectionType, naakud.domain"
         cascade="all-delete-orphan"
         inverse="true"
         access="field.camelcase-underscore">
      <key column="ParentId" foreign-key="fk_Category_ParentCategory" />
      <one-to-many class="Category"/>
    </set>
  </class>
</hibernate-mapping>


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="naakud.domain" namespace="naakud.domain">
  <class name="Product">
    <id name="Id">
      <generator class="hilo" />
    </id>
    <version name="Version" />
    <property name="Name" not-null="true" unique="true" />
    <property name="Description" not-null="true" />
    <property name="UnitPrice" not-null="true" type="Currency" />
    <many-to-one name="Category" column="CategoryId" />
  </class>
</hibernate-mapping>

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

Конфликт оператора DELETEс ограничением ССЫЛКА "fk_Category_Product".Конфликт произошел в базе данных "naakud", таблице "dbo.Product", столбце "CategoryId".Оператор был прерван.

Однако, когда я удаляю атрибут inverse = true в коллекции Products в отображении категории, он работает нормально.Для моего внешнего ключа CategoryId в таблице товаров установлено нулевое значение, и, следовательно, продукт не связан с категорией.Это то, что я хочу.

Я прочитал об обратном атрибуте, и я понимаю, что это означает, что он является стороной-владельцем отношений, а обновления / вставки / удаления выполняются в другом порядке, поэтому я считаю, что это решаетпроблема.Итак, мой вопрос, правильно ли я решаю свою проблему?Как это влияет на производительность?(не так много, я подозреваю).Было бы лучше иметь однонаправленные отношения без множества в одну сторону и установить для обратного атрибута значение true, чтобы повысить производительность?Или я схожу с ума и совершенно упускаю суть?

Ответы [ 2 ]

1 голос
/ 20 марта 2011

Другим способом решения проблемы удаления является установка для свойства many-to-one значения null для всех связанных объектов, равного null, перед сбросом.

Я могу придумать как минимум два способа сделать это:

  • В том же методе, который вызывает session.Delete(category), выполните:

    foreach (var product in category.Products)
        product.Category = null;
    
  • Использование HQL:

    session.CreateQuery(
           "update Product set Category = null where Category = :category")
           .SetParameter("category", category)
           .ExecuteUpdate();
    

Обновление

Вот реализация концепции с использованием прослушивателя событий.

0 голосов
/ 18 марта 2011

Я предполагаю, что вы читаете о Обратном атрибуте в NHibernate

Как говорится в сообщении об ошибке, ваш DELETE создает конфликт с ограничением внешнего ключа, что означает, что БД не может удалить категориюдо тех пор, пока есть продукты, ссылающиеся на эту конкретную категорию.

Что вы могли бы сделать (если можете изменить схему БД), так это применить «ON DELETE SET NULL» к ограничению вашего внешнего ключа.Таким образом, при выполнении DELETE БД автоматически установит все ссылки в таблице Product на NULL.

Если вы не можете изменить внешний ключ, у вас не будет иного выбора, кроме как удалить обратный атрибут.Это приведет к тому, что NHibernate сначала установит для ссылки Product.Category значение NULL, а затем удалит Category.

Если вам нужна Product.Category довольно часто, вам не следует избавляться от атрибута «многие к одному» в Product.

Относительно производительности, это зависит от того, как часто вы вставляете товары.Каждая вставка приведет к дополнительному обновлению для установки внешнего ключа.Это не должно быть проблемой, хотя.

...