【问题标题】:How do I properly update an NHibernate Many-to-Many relationship with Transient objects? TransientObjectException如何正确更新与瞬态对象的 NHibernate 多对多关系?瞬态对象异常
【发布时间】:2012-06-04 21:10:00
【问题描述】:

我在尝试保存属于多对多关联的对象时收到 TransientObjectException。我有点理解为什么会发生这种情况,但想了解如何正确完成我正在尝试做的事情。

简而言之,我想做的是:

我的应用程序有一个用户列表和一个角色列表。用户可以分配多个角色,角色可以分配给多个用户。有一个网页,管理员可以在其中执行这些分配,并且可以双向完成分配(例如,管理员可以选择一个用户,然后向其添加角色;或者,选择一个角色并向其添加用户)。

例如,假设管理员在用户“Alice”上单击“编辑”。管理员会看到可用角色列表和已分配给 Alice 的角色列表。然后管理员为 Alice 分配一个新角色并点击“保存”。

在服务器上,从客户端接收瞬态用户和分配的角色对象。如果我只是将临时角色列表分配给用户对象(例如,user.Roles = roles),我可以更新就好了。但是,如果我在进行此分配之前碰巧从数据库中读取了用户,我会在关联的角色对象上得到一个 TransientObjectException。

类定义:

public class Role
{
    public Guid ID { get; set; }
    public virtual IList<User> Users { get; set; }
}

public class User
{
    public Guid ID { get; set; }
    public virtual IList<Role> Roles { get; set; }
}

映射:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="Role">
    <id name="ID">
      <generator class="guid"/>
    </id>

    <bag name="Users" table="Role_User" lazy="false" cascade="none">
      <key column="RoleID" />
      <many-to-many column="UserID" class="User" />
    </bag>
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="User">
    <id name="ID">
      <generator class="guid"/>
    </id>

    <bag name="Roles" table="Role_User" lazy="false" cascade="none">
      <key column="UserID" />
      <many-to-many column="RoleID" class="Role" />
    </bag>
  </class>
</hibernate-mapping>

要保存的代码(有效)

public void UpdateUser(User user, IList<Role> associatedRoles)
{
    using (var session = _sessionFactory.OpenSession())
    {
        user.Roles = associatedRoles;
        session.Merge<User>(user);
    }
}

要保存的代码(失败)

public void UpdateUser(User user, IList<Role> associatedRoles)
{
    using (var session = _sessionFactory.OpenSession())
    {
        User originalUser = session.Get<User>(user.ID);
        // Code that does some audit reporting/logging
        LogDifferences(originalUser, user);
        user.Roles = associatedRoles;
        session.Merge<User>(user);
    }
}

对象是一个未保存的瞬态实例 - 在合并之前保存瞬态实例

【问题讨论】:

  • 我认为问题在于您的会话将有两个具有相同 ID 的用户实例。尝试使用 session.Evict() 从会话中删除 originalUser。

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


【解决方案1】:

Jouni Aro 是对的......您的会话中不能有两个具有相同 ID 的实体实例。

问题是以下之一:

  1. 您正在尝试从数据库加载 originalUser,但 user 已附加到同一会话
  2. 您已经加载了 originalUser,然后尝试将 user 合并到同一个会话 - 首先驱逐 originalUser,正如 Jouni 建议的那样

无论如何,您真的需要加载原始用户来了解更改内容吗? NHibernate 还有其他方法可以做到这一点。你试过interceptors吗?

这是一种可能的用法:http://nhforge.org/wikis/howtonh/finding-dirty-properties-in-nhibernate.aspxGoogle 更多信息。

【讨论】:

  • 我想这么多,这是我对 NHibernate 的主要烦恼之一 - 如果它可以检测到存在与非瞬态实例冲突的瞬态实例,为什么不自动执行驱逐?我不确定拦截器将如何在这里工作,因为这是在从客户端返回服务器的往返之后 - 服务器在没有进行某种比较的情况下没有关于单个更改值的上下文(如上面的代码中所示)。
  • 实际代码不直接处理会话(它被抽象掉了),所以我可能会以不同但类似的方式解决这个问题。我不会从会话中逐出对象,而是在并行会话中加载它,执行差异,然后关闭并行会话。
  • 我认为您遇到了案例 #2。尝试将用户合并到已具有非瞬态 originalUser 的同一会话。如果 NHibernate 做了一些自动驱逐,你的 originalUser 将被踢出,用户将被合并。它可能适用于您的情况,但可能会在其他人的代码中导致一些微妙的、难以跟踪/调试的错误。
  • 您的会话是否被抽象并不重要(这是一个优点)。您将使用 NHibernate 的 Configuration 对象注入自定义拦截器。在这个新的 Save 拦截器中,您可以只处理已修改的 User 实体并记录您的差异。保存拦截器在非常低的级别启动......就在更改持久化到数据库之前。
  • 意味着拦截器会执行上述操作(例如,加载原件,执行审计,驱逐,然后保存)?我可以试试。
【解决方案2】:

Miroslav Popovic 的观点高度相关;但是,就我而言,由于数据库中的实体将版本设置为空 guid,因此我遇到了问题。这些记录是在 NHibernate 之外创建的。在保存它们之间的关联时,NHibernate 失败了,因为它检测到其中一个关联条目是由于其版本为 0 的瞬态对象,即使它引用了现有项目。

【讨论】:

    猜你喜欢
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    • 2019-06-28
    • 1970-01-01
    • 2010-12-21
    • 1970-01-01
    相关资源
    最近更新 更多