What is the correct way to define many-to-many relationships in NHibernate to allow deletes but avoiding duplicate records

匆匆过客 提交于 2019-11-28 05:34:36

What you need to do in order to have your mappings work as you would expect them to, is to move the inverse="true" from the Category.Items collection to the Item.Categories collection. By doing that you will make NHibernate understand which one is the owning side of the association and that would be the "Category" side.

If you do that, by deleting a Category object it would delete the matching record from the lookup table as you want it to as it is allowed to do so because it is the owning side of the association.

In order to NOT delete the Items that are assigned to a Category object that is to be deleted you need to leave have the cascade attribe as: cascade="save-update".

cascade="all" will delete the items that are associated with the deleted Category object.

A side effect though would be that deleting the entity on the side where the inverse=tru exists will thow a foreign key violation exception as the entry in the association table is not cleared.

A solution that will have your mappings work exactly as you want them to work (by the description you provided in your question) would be to explicitly map the association table. Your mappings should look like that:

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

  <class name="Category" lazy="true">

    <id name="CategoryId" unsaved-value="0">
      <generator class="native" />
    </id>
    <property name="Name" />

    <bag name="ItemCategories" generic="true" inverse="true" lazy="true" cascade="none">
        <key column="CategoryId"/>
        <one-to-many class="ItemCategory"/>
    </bag>

    <bag name="Items" table="ItemCategory" cascade="save-update" generic="true">
      <key column="CategoryId"></key>
      <many-to-many class="Item" column="ItemId"></many-to-many>
    </bag>

  </class>

</hibernate-mapping>

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

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

    <id name="ItemId" unsaved-value="0">
      <generator class="native" />
    </id>
    <property name="Name" />

    <bag name="ItemCategories" generic="true" inverse="true" lazy="true" cascade="all-delete-orphan">
        <key column="ItemId"/>
    <one-to-many class="ItemCategory"/>
    </bag>

    <bag name="Categories" table="ItemCategory" inverse="true" cascade="save-update" generic="true">
      <key column="ItemId"></key>
      <many-to-many class="Category" column="CategoryId"></many-to-many>
    </bag>

  </class>

</hibernate-mapping>

As it is above it allows you the following:

  1. Delete a Category and only delete the entry in the association table without deleting any of the Items
  2. Delete an Item and only delete the entry in the association table without deleting any of the Categories
  3. Save with Cascades from only the Category side by populating the Category.Items collection and saving the Category.
  4. Since the inverse=true is necessary in the Item.Categories there isn't a way to do cascading save from this side. By populating the Item.Categories collection and then saving the Item objec you will get an insert to the Item table and an insert to the Category table but no insert to the association table. I guess this is how NHibernate works and I haven't yet found a way around it.

All the above are tested with unit tests. You will need to create the ItemCategory class mapping file and class for the above to work.

Are you keeping the collections in synch? Hibernate expects you, I believe, to have a correct object graph; if you delete an entry from Item.Categories, I think you have to delete the same entry from Category.Items so that the two collections are in sync.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!