NHibernate:多对多关系无法首先保存子对象(要么:“不能插入空值”或:“瞬态对象”)

发布于 2024-08-12 21:05:36 字数 1239 浏览 2 评论 0原文

我有一个聚合 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>

将cascade设置为“all”会产生:

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

将cascade设置为“none”会产生:

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

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

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

I have a consignment class that aggregates a FreightDateTime class. At the same time, the FreightDateTime class is also aggregated by the GoodsItem class. In the same manner, FreightDateTime is associated with a number of other classes that I left out for now.

To avoid a databasetable FreightDateTime with ConsignmentId foreign key, a GoodsItemId foreign key, etc. I decided that the association should be many-to-many. This way, NHibernate would generate an association table for each relationship instead (ConsigmentFreightDateTimes, GoodsItemFreightDateTimes), which makes more sense.

So, in the mapping file, the association looks e.g. like this:

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

Setting cascade to "all" yields:

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

Setting cascade to "none" yields:

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

In both cases, this means that NHibernate is trying to save the Consignment instance, although the child FreightDateTime instances have not been saved. In the first case, the foreign key is still 'null', which thus cannot be inserted in the resulting table, and in the second case, NHibernate is aware that the instance has not yet been saved, and thus throws the exception.

So the question is how I can get NHibernate to save all child instances first without explicitly telling it to do so. I have hunch that allowing nulls on column DateTimeId would do the trick, but I think that is neither desirable nor possible.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

深海蓝天 2024-08-19 21:05:36

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

另外,我在这里回答了类似的问题:在 NHibernate 中定义多对多关系以允许删除但避免重复记录的正确方法是什么

阅读问题和我的答案可能会帮助您了解发生了什么与您的多对多关联,也许会给您解决方案的提示。最后的建议是最后的手段。

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

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

以上均通过单元测试进行测试。
您需要创建 PersonNote 类映射文件和类才能使上述工作正常进行。

如果您需要另一个类来包含注释(假设为“组织”),那么您只需将另一个关联表添加到您的架构中,称为“组织注释”,并对“组织”映射文件和“注释”映射文件执行与上面相同的操作。

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

Try to map the association on the other side also but use the inverse="true" attribute on that side. So, create in the FreightDateTime mapping file a bag to map the association as many-to-many with the Consignment.

Also, I have answered a similar question here: What is the correct way to define many-to-many relationships in NHibernate to allow deletes but avoiding duplicate records

Reading the question and my answer maybe will help you understand what is going on with your many-to-many association and maybe give you a hint for the solution. The final recommendation is kind of last resort.

The answer in the above question is just to give an idea of what is going through another person's different issue.

A solution would be to explicitly map the association table.
If your tables are: Person, Note and the association table (X table) is PersonNote
Your mappings should look like that:

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

As it is above it allows you the following:

  1. Delete a Person and only delete the entry in the association table without deleting any of the Notes
  2. Delete an Note and only delete the entry in the association table without deleting any of the Person entities
  3. Save with Cascades from only the Person side by populating the Person.Notes collection and saving the Person.
  4. Since the inverse=true is necessary in the Note.People there isn't a way to do cascading save from this side. By populating the Note.People collection and then saving the Note object you will get an insert to the Note table and an insert to the Person 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.
  5. You can cascade save entries in the association table only explicitly by adding new items in the PersonNotes collection of the Note entity.

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

If you need another class to have notes lets say Organisation then you only ad another association table to your schema called OrgnanisationNote and you do the same as above to the Organisation mapping file and the Note mapping file.

I must say again that this should be the final option for anyone who want to have complete control over his/hers many-to-many associations.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文