【问题标题】:NHibernate: many-to-many relation fails to save child objects first (either: "cannot insert null" or: "transient object")NHibernate:多对多关系无法首先保存子对象(要么:“无法插入空”或:“瞬态对象”)
【发布时间】:2010-12-21 15:34:08
【问题描述】:

我有一个聚合 FreightDateTime 类的寄售类。同时,FreightDateTime 类也被 GoodsItem 类聚合。以同样的方式,FreightDateTime 与我暂时忽略的许多其他类相关联。

为了避免使用 ConsignmentId 外键、GoodsItemId 外键等的数据库表 FreightDateTime。我决定关联应该是多对多的。这样,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. 

将级联设置为“无”产生:

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

在这两种情况下,这意味着 NHibernate 正在尝试保存 Consignment 实例,尽管尚未保存子 FreightDateTime 实例。在第一种情况下,外键仍然是'null',因此不能插入到结果表中,而在第二种情况下,NHibernate 知道该实例尚未保存,因此抛出异常。

所以问题是如何让 NHibernate 先保存所有子实例而不明确告诉它这样做。我有预感,允许 DateTimeId 列上的空值可以解决问题,但我认为这既不可取也不可能。

【问题讨论】:

    标签: c# nhibernate many-to-many transient


    【解决方案1】:

    尝试在另一侧也映射关联,但在该侧使用 inverse="true" 属性。因此,在 FreightDateTime 映射文件中创建一个包,以将关联映射为与寄售的多对多。

    另外,我在这里回答了一个类似的问题:What is the correct way to define many-to-many relationships in NHibernate to allow deletes but avoiding duplicate records

    阅读问题和我的回答可能会帮助您了解多对多关联的情况,并可能为您提供解决方案的提示。最后的建议是最后的手段。

    上述问题的答案只是为了让您了解另一个人的不同问题正在经历什么。

    一种解决方案是显式映射关联表。 如果您的表是:Person、Note 和关联表(X 表)是 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.Notes 集合并保存 Person,仅从 Person 端使用 Cascades 保存。
    4. 由于在 Note.People 中 inverse=true 是必需的,因此无法从这一侧进行级联保存。通过填充 Note.People 集合,然后保存 Note 对象,您将获得对 Note 表的插入和对 Person 表的插入,但没有对关联表的插入。我想这就是 NHibernate 的工作原理,我还没有找到解决方法。
    5. 您只能通过在 Note 实体的 PersonNotes 集合中添加新项目来显式地级联关联表中的保存条目。

    以上所有内容都通过单元测试进行了测试。 您将需要创建 PersonNote 类映射文件和类才能使上述工作正常工作。

    如果您需要另一个类来拥有注释,比如说组织,那么您只需将另一个关联表添加到您的架构中,称为 OrgnanisationNote,并且您对组织映射文件和注释映射文件执行与上述相同的操作。

    我必须再说一遍,这应该是任何想要完全控制他/她的多对多关联的人的最终选择。

    【讨论】:

    • 向 FreightDateTime 映射添加一个反包将有 NHibernate 要求 FreighDateTime 类中也有一个对应的 IList 属性。我的目标是避免在类和表级别将 FreightDateTime 引用到寄售,因为 FreightDateTime 不需要“知道”任何有关拥有它的对象的信息。怎么可能实现?
    • 我相信没有办法解决它。您必须创建 inverse bag 才能使映射按您希望的方式工作。 NHibernate 允许您将包映射到私有成员,而不是无法从实体实现外部访问的公共属性(access="field" 属性)。如果您仍然坚持不映射逆,那么只需尝试看看它是否确实有效。然后你可以让它工作,直到你找到适合你的“完美”解决方案(如果有的话)。
    • 谢谢。我想我宁愿有一堆一对多的关系,这可能会导致 FreightDateTimes 表引用很多不同的其他表,这些表彼此不相关,但暂时就这样吧。我还研究了复合元素和多对任意,但它们适用于不同的情况。或者你怎么看?
    • 我也可以尝试一种不同的方法,完全取消 FreightDateTime 类并改用复合元素。这样做的背面是我的数据库会被小表弄得乱七八糟,但无论如何我设想的关联表都是这样......
    • 在我看来,显式映射关联表 (FreightDateTimes) 并使用关联表将多对多关系转换为 2 个一对多(每个表一个)始终是一个不错的选择.正如我在回答其他相关问题时提出的那样,如果使用得当,它可以给你带来很多好处。
    猜你喜欢
    • 2012-06-04
    • 1970-01-01
    • 1970-01-01
    • 2018-11-16
    • 2018-02-10
    • 1970-01-01
    • 2020-07-22
    • 1970-01-01
    • 2012-12-05
    相关资源
    最近更新 更多