当我更新实体时,如何处理NHibernate抛出“NonUniqueObjectReferenceException”

本文关键字:NHibernate 处理 抛出 NonUniqueObjectReferenceException 实体 更新 何处理 | 更新日期: 2023-09-27 18:23:37

我使用ISession.Query<T>().ToList()获取对象列表,将它们添加到组合框中,以便用户可以浏览所有对象,然后编辑其中一个对象,并使用该对象调用ISession.Update()。然而,这抛出了一个NonUniqueObjectReferenceException

为什么会发生这种情况,我应该使用什么方法来避免/解决这种情况?

当我更新实体时,如何处理NHibernate抛出“NonUniqueObjectReferenceException”

问题说明

加载实体时,Session会在Session期间跟踪实体的数据库主键以及实体的对象reference(内存中的位置)。

如果尝试将具有相同主键但对象reference不同的实体持久化为该Session的已加载实体,则会引发NonUniqueObjectReferenceException

换句话说,Session告诉你,"我在内存中有一个实体,它的主键与你试图持久化的实体相同,但我的副本的对象reference与你的副本不匹配。"

问题示例

  1. 打开Session#1
  2. 加载实体(对象引用=A,数据库中的主键=1
  3. 关闭Session
  4. 打开一个新的Session#2
  5. 再次加载同一实体(这一次,对象引用=B,数据库中的主键=1
  6. 更改对象a的属性并将其持久化到Session#2中
  7. 将抛出NonUniqueObjectReferenceException

值得注意的是,即使对象A只是会话2中持久化的较大对象图的一部分(即使对象A没有更改),也会抛出此异常。

还值得注意的是,您可以直接加载实体(Session.LoadSession.GetSession.QueryOver等)或间接加载实体(使用不返回对象但会将对象加载到内存中的查询)。NonUniqueObjectReferenceException既可以用于直接加载的实体,也可以用于直接装载的实体。

重要提示:此问题可能由其他方面引起,例如,如果加载并克隆实体,然后使用此克隆使用Session保留某些更改,则可能由单个Session引起。原因是,克隆的对象reference可能与原始实体不同。

解决方案说明

Session对象上有一个名为Merge的方法:

object Session.Merge(object obj)

Merge将获取一个实体,并使用该实体的主键从当前Session中检索该实体的已加载版本。如果Session的已加载实体的属性与您刚刚传递的实体不同,它还会更新这些实体的属性。

此方法不会更改传入的实体,而是返回一个您应该使用的不同对象。

关于Merge的最后一个注意事项是,如果您所在的Session内存中没有加载该实体的副本,那么Merge将继续从数据库中加载该实体,然后再执行其通常的合并功能。

解决方案示例

// using the example above, we are at the beginning of step 6 inside session #2
// we have 2 important objects = ISession sessionTwo, Option objectA.
// Option is an entity defined by you, it is not part of NH.
objectA.SomeProperty = "blah";
var optionFromSessionTwo = (Option) sessionTwo.Merge(objectA);
// this will not throw and it will persist the changes to objectA
sessionTwo.Flush();

希望这能有所帮助!

Denis所说的Update方法用于分离对象的持久化。抛出此异常可能是因为您正在尝试对会话中可能已经存在的对象使用Update()。您应该使用事务或Flush()来更新您的对象,如果您已经这样做了;从代码中删除session.Update()就可以了。

这是NHibernate文档中适当的部分。

您需要阅读以下文档:

  • 如何在Winforms上使用NHibernate
  • "更新"的含义

快速解决方法是:不要调用session.Update(),只调用session.Flush(),这样更改就会反映到DB中。

session.Update()不更新实体,会话透明地进行更新。Update和Save方法与INSERT和Update无关,相反,Save()使瞬态对象持久化,而Update()使分离的对象持久化。