在 NHibernate 中定义多对多关系以允许删除但避免重复记录的正确方法是啥
Posted
技术标签:
【中文标题】在 NHibernate 中定义多对多关系以允许删除但避免重复记录的正确方法是啥【英文标题】:What is the correct way to define many-to-many relationships in NHibernate to allow deletes but avoiding duplicate records在 NHibernate 中定义多对多关系以允许删除但避免重复记录的正确方法是什么 【发布时间】:2009-11-08 01:38:06 【问题描述】:几天来,我一直在与 NHibernate 设置作斗争,只是想不出正确的方法来设置我的映射,所以它就像我期望的那样工作。
在我解决问题之前,有一些代码需要完成,所以提前为额外阅读表示歉意。
目前设置非常简单,只有这些表格:
类别 类别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);
现在已经解决了,我遇到的问题是:
如果我从 Category.Items 映射中删除 'inverse="true"',我会在查找 ItemCategory 表中得到重复的条目。
使用'inverse="true"' 时,当我尝试删除一个类别时出现错误,因为NHibernate 不会从查找表中删除匹配的记录,因此由于外键约束而失败。
如果我在包上设置 cascade="all",我可以删除而不会出错,但删除类别也会删除该类别中的所有项目。
我的映射设置方式是否存在一些基本问题,以允许多对多映射按您预期的方式工作?
通过“您所期望的方式”,我的意思是删除不会删除比被删除的项目和相应的查找值(使关系的另一端的项目不受影响)和更新到任一集合之外的任何内容将使用正确且不重复的值更新查找表。
任何建议都将受到高度赞赏。
【问题讨论】:
【参考方案1】:要使映射按预期工作,您需要做的是将inverse="true"
从Category.Items
集合移动到Item.Categories
集合。通过这样做,您将使 NHibernate 了解哪个是关联的拥有方,那将是“类别”方。
如果您这样做,通过删除 Category 对象,它会根据您的需要从查找表中删除匹配的记录,因为它是关联的拥有方,因此允许这样做。
为了不删除分配给要删除的类别对象的项目,您需要将级联属性保留为: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>
如上所示,它允许您执行以下操作:
-
删除一个类别,只删除关联表中的条目,而不删除任何项
删除一个Item,只删除关联表中的条目,不删除任何Categories
通过填充 Category.Items 集合并保存 Category,仅从 Category 端使用 Cascades 保存。
由于在 Item.Categories 中需要 inverse=true,因此无法从这一侧进行级联保存。通过填充 Item.Categories 集合,然后保存 Item 对象,您将获得对 Item 表的插入和对 Category 表的插入,但没有对关联表的插入。我想这就是 NHibernate 的工作原理,我还没有找到解决方法。
以上所有内容都通过单元测试进行了测试。 您需要创建 ItemCategory 类映射文件和类才能使上述工作正常工作。
【讨论】:
如果我按照建议反转“inverse='true'”,我可以按预期删除类别,但我遇到的问题与之前相反:尝试删除时出现错误一个项目,因为它不会删除查找值。 我对该主题进行了更多调查,并编辑了我的答案以包含我的发现。 感谢您的更新 - 这是一个巨大的帮助。抱歉,回复晚了,我没有收到电子邮件通知我您已更新答案。 如我所见,我不必在单独的 hbm xml 文件中映射关系表,对吗? @Rookian 在上面的示例中,ItemCategory 数据库表映射到它自己的 XML 文件中,它有 2 个多对一属性,一个指向 Item 表,一个指向 Category 表它们是 Item 类映射文件(ItemCategories 属性)和 Category 类映射文件(ItemCategories 属性)中的一对多属性的对立面。【参考方案2】:您是否使集合保持同步?我相信 Hibernate 期望你有一个正确的对象图。如果您从 Item.Categories 中删除一个条目,我认为您必须从 Category.Items 中删除相同的条目,以便两个集合同步。
【讨论】:
我有类似于上面示例中的 add 方法的 remove 方法,这些方法都可以,只是删除了一个我遇到问题的实际对象。我想我可以在删除对象时遍历对象中相关项的集合,但我认为 NHibernate 应该选择它。 通常我会将这种关联映射到可以删除的实际 ItemCategory 对象。我不确定像您这样的直接关联,如何解决它。抱歉,我无法提供更多帮助。 =/ 那么,如果我创建了一个 ItemCategory 对象和映射,这是否意味着 Item 和 Category 都将与 ItemCategory 具有一对多关联,并且还具有 ItemCategory 对象的集合?然后我会做类似 Item.ItemCategories.Categories 的事情来获取项目的类别吗?或者,是否可以使用 ItemCategory 映射对象,但仅使用 Item.Categories 和 Category.Items 保持域模型“干净”? 一个ItemCategory 对象将映射到一个Item 和一个Category,就像您为表架构使用它一样。每个 Category 都有一个 ItemCategories 的集合,每个 Item 都有一个 ItemCategories 的集合;所以它将是 Item.ItemCategories[i].Category。您可以想象实现一个辅助方法 Item.Categories 以便于使用。可以在没有 ItemCategory 对象的情况下直接执行此操作,就像您最初放置的方式一样,但恕我直言,我也不知道细节。以上是关于在 NHibernate 中定义多对多关系以允许删除但避免重复记录的正确方法是啥的主要内容,如果未能解决你的问题,请参考以下文章
Fluent NHibernate:如何在关系表上映射具有附加属性的多对多关系?
[C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例