NHibernate: отношение «многие ко многим» не может сначала сохранить дочерние объекты (либо: «невозможно вставить ноль», либо: «временный объект») - PullRequest
0 голосов
/ 30 ноября 2009

У меня есть класс консигнации, который агрегирует класс FreightDateTime. В то же время класс FreightDateTime также агрегируется классом GoodsItem. Точно так же FreightDateTime связан с рядом других классов, которые я пока оставил.

Чтобы избежать создания базы данных FreightDateTime с внешним ключом ConsignmentId, внешним ключом GoodsItemId и т. Д. Я решил, что ассоциация должна быть «многие ко многим». Таким образом, NHibernate вместо этого будет генерировать таблицу ассоциации для каждого отношения (ConsigmentFreightDateTimes, GoodsItemFreightDateTimes), что имеет больше смысла.

Итак, в файле отображения ассоциация выглядит, например, как это:

<bag name="DateTimes" table="FreightDateTimes" lazy="false" cascade="all">
  <key column="ConsignmentId"/>
  <many-to-many class="Logistics.FreightDateTime, Logistics" column="DateTimeId" />
</bag>

Установка каскада на «все» выходы:

System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'DateTimeId', table 'LogiGate.dbo.FreightDateTimes'; column does not allow nulls. INSERT fails. 

Установка каскада в "none" приводит к:

NHibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: Logistics.FreightDateTime

В обоих случаях это означает, что NHibernate пытается сохранить экземпляр консигнации, хотя дочерние экземпляры FreightDateTime не были сохранены. В первом случае внешний ключ все еще имеет значение «ноль», что, таким образом, не может быть вставлено в результирующую таблицу, а во втором случае NHibernate осознает, что экземпляр еще не сохранен, и, таким образом, выдает исключение.

Таким образом, вопрос в том, как мне заставить NHibernate сначала сохранить все дочерние экземпляры без явного указания сделать это. У меня есть предчувствие, что пустые значения в столбце DateTimeId помогут, но я думаю, что это не желательно и не возможно.

1 Ответ

1 голос
/ 30 ноября 2009

Попробуйте сопоставить ассоциацию и на другой стороне, но используйте атрибут inverse = "true" на этой стороне. Итак, создайте в файле сопоставления FreightDateTime пакет, чтобы сопоставить сопоставление «многие ко многим» с накладной.

Кроме того, я ответил на аналогичный вопрос здесь: Как правильно определить отношения «многие ко многим» в NHibernate, чтобы разрешить удаление, но избегая дублирования записей

Чтение вопроса и моего ответа, возможно, поможет вам понять, что происходит с вашей ассоциацией «многие ко многим», и, возможно, даст вам подсказку для решения. Окончательная рекомендация является последним средством.

Ответ на поставленный выше вопрос состоит в том, чтобы просто дать представление о том, что переживает другой вопрос другого человека.

Решением было бы явное отображение таблицы ассоциации. Если ваши таблицы: Person, Note и таблица ассоциации (X table) - PersonNote Ваши отображения должны выглядеть так:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="..."
                   namespace="...">

  <class name="Person" table="Person" lazy="true">

    <id name="PersonId">
      <generator class="native" />
    </id>
    <property name="FirstName" />
    .....
    <bag name="PersonNotes" generic="true" inverse="true" lazy="true" cascade="none">
        <key column="PersonId"/>
        <one-to-many class="PersonNote"/>
    </bag>

    <bag name="Notes" table="PersonNote" cascade="save-update">
      <key column="PersonId"></key>
      <many-to-many class="Note" column="NoteId"></many-to-many>
    </bag>

  </class>

</hibernate-mapping>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="..."
                   namespace="...">

  <class name="Note" table="Note" lazy="true">

    <id name="NoteId" unsaved-value="0">
      <generator class="native" />
    </id>
    <property name="Title" />
    ....
    <bag name="PersonNotes" inverse="true" lazy="true" cascade="all-delete-orphan">
        <key column="NoteId"/>
        <one-to-many class="PersonNote"/>
    </bag>

    <bag name="People" table="PersonNote" inverse="true" cascade="save-update" generic="true">
      <key column="NoteId"></key>
      <many-to-many class="Person" column="PersonId"></many-to-many>
    </bag>

  </class>

</hibernate-mapping>

Как указано выше, оно позволяет вам следующее:

  1. Удалить человека и удалить только запись в таблице сопоставлений, не удаляя ни одной из заметок
  2. Удалить примечание и удалить только запись в таблице сопоставлений, не удаляя ни одного из лиц Person
  3. Сохраняйте с помощью каскадов только со стороны Person, заполняя коллекцию Person.Notes и сохраняя Person.
  4. Поскольку обратное = true необходимо в Note.People нет способа сделать каскадное сохранение с этой стороны. Заполнив коллекцию Note.People и сохранив объект Note, вы получите вставку в таблицу заметок и вставку в таблицу Person, но не вставку в таблицу сопоставлений. Полагаю, именно так работает NHibernate, и я пока не нашел способа обойти это.
  5. Вы можете каскадно сохранять записи в таблице ассоциаций только явно, добавляя новые элементы в коллекцию PersonNotes сущности Note.

Все вышеперечисленное проверено с помощью модульных тестов. Вам нужно будет создать файл отображения класса PersonNote и класс для работы вышеупомянутого.

Если вам нужен другой класс для заметок, скажем, «Организация», тогда вы добавляете в таблицу только одну ассоциативную таблицу OrgnanisationNote и делаете то же, что и выше, для файла сопоставления организации и файла сопоставления заметки.

Я должен еще раз сказать, что это должен быть последний вариант для любого, кто хочет иметь полный контроль над его / ее ассоциациями "многие ко многим".

...