2

我想处理用户在 Windows 窗体应用程序中从数据库编辑对象,进行违反数据库约束(即列唯一值)的编辑,将实体保存回数据库,NHibernate 抛出的情况一个例外,从而破坏了会话。

我正在使用 MSDN 文章使用 NHibernate 构建桌面待办事项应用程序中发布的指南,并使用每个演示者/表单的会话方法。在创建演示者时,我保留对 SessionFactory 的引用,创建一个新会话,通过会话从数据库中检索对象并存储对它的引用 [对象]。显示表单时,其字段由对象填充。

当对表单进行更改并且用户希望保存数据时,从字段值更新对象,并将对象保存回数据库。

我处理过时状态异常,其中对象在从数据库检索到它被保存回来的时间之间发生了更改:创建一个新会话,再次加载原始对象,通知用户发生了冲突,并且显示他的更改以及数据库中当前的内容。他可以选择保存他的更改或取消(并接受现在存储在数据库中的内容)。

在违反约束的情况下,似乎会发生以下两种情况之一:

  1. 该对象在数据库中没有更改,可以将其重新加载到新会话中。
  2. 该对象在数据库中也已更改,不再反映最初加载的内容。

但是,我认为我实际上无法检测到发生了哪种情况,因为由于发生了约束异常(我已经对此进行了测试),所以永远不会抛出陈旧状态异常。

处理案例 1 是微不足道的,因为我可以简单地显示一条错误消息,上面写着“FIELD-X 的值已经在数据库中”,并假装没有发生任何真正糟糕的事情。用户可以将 FIELD-X 更改为唯一值并再次保存,而无需重新输入更改。

处理案例 2 就像处理常规的陈旧状态异常。

我知道我可以通过保留原始副本(或其值)然后逐个字段比较这两个来“蛮力”这一点。但是,我怀疑有更好的方法来利用 NHibernate 来处理这个问题。 你会怎么处理这个?

如果有帮助:

  • NHibernate 版本:3.2
  • 使用“脏”策略的乐观锁定
  • .NET 框架 2.0 SP2
  • SQLite 数据库/驱动程序

编辑 2 月 23 日 - 我添加了一些示例代码以更好地说明我目前在做什么。

/**
 * Save handler called when user initiates save in UI.
 * 
 * Returns true when save was successful (essentially, tells the presenter
 * that the UI can be closed.
 */
private bool SaveData()
{
    try
    {
        if (this.creatingNewUserAccount)
        {
            // Do whatever is necessary to instantiate a new object.
            this.userAccount = new UserAccount();
            // and copy values from the UI into the new object.
            this.userAccount.Name = this.form.Name;
            // etc.
        }
        else
        {
            // Just copy values from the UI into the existing object
            // from the database.
            this.userAccount.Name = this.form.Name;
            // etc.
        }

        using (ITransaction tx = this.session.BeginTransaction())
        {
            this.accountRepository.store(this.userAccount);
            tx.Commit();
        }

        return true;
    }
    catch (StaleObjectStateException)
    {
        HandleStaleStateException();
        return false;
    }
    catch (ArgumentException e)
    {
        this.m_View.ShowOtherDialog(e.Message);
        return false;
    }
    catch (GenericADOException e)
    {
        HandleConstraintViolationException();
        return false;
    }
}

private void HandleStaleStateException()
{
    // The session was trashed when the exception was thrown,
    // so close it and create a new one.
    this.session.Dispose();
    this.session = this.sessionFactory.OpenSession();
    CurrentSessionContext.Bind(this.session);

    // Reload the object from the database.
    this.userAccount = LoadData();

    // Do a bunch of things that deal with informing the user
    // of the stale-state and displaying a form to merge changes.
    HandleEditConflict();
}

private void HandleConstraintViolationException()
{
    // The session was trashed when the exception was thrown,
    // so close it and create a new one.
    this.session.Dispose();
    this.session = this.sessionFactory.OpenSession();
    CurrentSessionContext.Bind(this.session);

    // Determine if trying to save a new entity or editing an existing one.
    if (this.creatingNewUserAccount)
    {
        // If saving a new entity, we don't care about the old object
        // we created and tried to save.
        this.userAccount = null;
    }
    else
    {
        // ????
    }
}
4

2 回答 2

1

ISession.Refresh(Object obj)方法最终对我有用。除了最终方法外,我的问题中的代码保持不变:

private void HandleConstraintViolationException()
{
    // The session was trashed when the exception was thrown,
    // so close it and create a new one.
    this.session.Dispose();
    this.session = this.sessionFactory.OpenSession();
    CurrentSessionContext.Bind(this.session);

    // Determine if trying to save a new entity or editing an existing one.
    if (this.creatingNewUserAccount)
    {
        // If saving a new entity, we don't care about the old object
        // we created and tried to save.
        this.userAccount = null;
    }
    else
    {
        this.session.Refresh(this.userAccount);
    }
    this.form.ShowDialog("... Describe the constraint violation ...");
}
于 2012-03-05T21:43:59.827 回答
0

你可以

// after ConstraintException with new session

session.Lock(loadedObject, LockMode.None);  // attach object with session
// copy back from UI
session.Flush();
catch()
{
    if (ConstraintException)
        // repeat
    else if (Stale)
        // handle like you have
    else
        // all ok
}

如果您对 db 中的内容不感兴趣

// loads from the database, copy state from object into it and returns the loaded object (attached to session), changes will be updated on next flush
obj = session.Merge(obj);
于 2012-02-23T08:29:00.773 回答