使用NHibernate在Winforms中处理约束冲突

时间:2012-02-22 20:35:24

标签: winforms nhibernate constraints staleobjectstate

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

我正在使用MSDN文章Building a Desktop To-Do Application with NHibernate中发布的指南,并使用每个演示者会话/表单方法。在创建演示者时,我保留对SessionFactory的引用,创建一个新会话,通过会话从数据库中检索对象并存储对它的引用[对象]。显示表单时,将从对象填充其字段。

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

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

如果违反约束,似乎可能发生以下两种情况之一:

  1. 数据库中的对象没有更改,可以将其重新加载到新会话中。
  2. 该对象在数据库中也已更改,不再反映最初加载的内容。
  3. 但是,我认为我实际上无法检测到哪种情况发生,因为从未抛出过时状态异常,因为发生了约束异常(我已经测试了这个)。

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

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

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

    如果有帮助:

    • NHibernate版本:3.2
    • 使用“脏”策略进行乐观锁定
    • .NET Framework 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
        {
            // ????
        }
    }
    

2 个答案:

答案 0 :(得分: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 ...");
}

答案 1 :(得分: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);