在NHibernate中定义多对多关系以允许删除但避免重复记录的正确方法是什么

时间:2009-11-08 01:38:07

标签: nhibernate nhibernate-mapping many-to-many

我一直在与NHibernate的设置斗争了几天,并且无法找出正确的方法来设置我的映射,所以它的工作方式就像我期望的那样。

在我遇到问题之前需要经过一些代码,所以请提前为额外阅读道歉。

目前设置非常简单,只有这些表:

分类
类别编号
名称

物品
项目Id
姓名

ItemCategory
项目Id
类别ID

项目可以分为多个类别,每个类别可以包含多个项目(简单的多对多关系)。

我的映射设置如下:

<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="Items" table="ItemCategory" cascade="save-update" inverse="true" 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="Categories" table="ItemCategory" cascade="save-update" generic="true">
      <key column="ItemId"></key>
      <many-to-many class="Category" column="CategoryId"></many-to-many>
    </bag>

  </class>

</hibernate-mapping>

我在物品的类别和类别列表中的项目列表中添加项目的方法设置了关系的两侧。

项目

    public virtual IList<Category> Categories { get; protected set; }
    public virtual void AddToCategory(Category category)
    {
        if (Categories == null)
            Categories = new List<Category>();

        if (!Categories.Contains(category))
        {
            Categories.Add(category);
            category.AddItem(this);
        }
    }

类别

    public virtual IList<Item> Items { get; protected set; }
    public virtual void AddItem(Item item)
    {
        if (Items == null)
            Items = new List<Item>();

        if (!Items.Contains(item))
        {
            Items.Add(item);
            item.AddToCategory(this);
        }
    }

现在已经不在了,我遇到的问题是:

  1. 如果我从Category.Items映射中删除'inverse =“true”',我会在查找ItemCategory表中获得重复的条目。

  2. 当使用'inverse =“true”'时,当我尝试删除类别时出现错误,因为NHibernate没有从查找表中删除匹配的记录,因此由于外键约束而失败。

  3. 如果我在行李上设置cascade =“all”,我可以删除而不会出现错误,但删除类别也会删除该类别中的所有项目。

  4. 我的映射设置方式是否存在一些基本问题,以允许多对多映射按预期工作?

    按照“您期望的方式”,我的意思是删除不会删除除被删除的项目和相应的查找值(使关系另一端的项目不受影响)以及对任一集合的更新将使用正确和非重复的值更新查找表。

    任何建议都将受到高度赞赏。

2 个答案:

答案 0 :(得分:10)

为了让映射按照您的预期运行,您需要做的是将inverse="true"Category.Items集合移动到Item.Categories集合。通过这样做,你将使NHibernate了解哪一个是关联的拥有方,那将是“类别”方面。

如果你这样做,通过删除一个Category对象,它会根据你的意愿从查找表中删除匹配的记录,因为它是关联的拥有方。

为了不删除分配给要删除的Category对象的Items,您需要将cascade attribe保留为:cascade="save-update"

cascade="all"将删除与已删除的Category对象关联的项目。

虽然副作用是删除inverse = tru存在的那一侧的实体会引发外键违规异常,因为关联表中的条目不会被清除。

使映射完全按照您希望的方式工作的解决方案(通过您在问题中提供的描述)将显式映射关联表。 你的映射应该是这样的:

<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>

如上所述,它允许您:

  1. 删除类别,仅删除关联表中的条目而不删除任何项目
  2. 删除项目,仅删除关联表中的条目而不删除任何类别
  3. 通过填充Category.Items集合并保存类别,仅从类别侧保存Cascades。
  4. 由于Item.Categories中有必要使用inverse = true,因此无法从此方面进行级联保存。通过填充Item.Categories集合然后保存Item objec,您将获得Item表的插入和Category表的插入,但不会插入到关联表。我想这就是NHibernate的工作方式,我还没有找到解决办法。
  5. 以上所有都是通过单元测试进行测试的。 您需要创建ItemCategory类映射文件和类,以使上述工作。

答案 1 :(得分:1)

您是否保持同步系列?我相信Hibernate期望你有一个正确的对象图;如果你从Item.Categories中删除一个条目,我认为你必须从Category.Items删除相同的条目,以便两个集合同步。