我想处理用户在 Windows 窗体应用程序中从数据库编辑对象,进行违反数据库约束(即列唯一值)的编辑,将实体保存回数据库,NHibernate 抛出的情况一个例外,从而破坏了会话。
我正在使用 MSDN 文章使用 NHibernate 构建桌面待办事项应用程序中发布的指南,并使用每个演示者/表单的会话方法。在创建演示者时,我保留对 SessionFactory 的引用,创建一个新会话,通过会话从数据库中检索对象并存储对它的引用 [对象]。显示表单时,其字段由对象填充。
当对表单进行更改并且用户希望保存数据时,从字段值更新对象,并将对象保存回数据库。
我处理过时状态异常,其中对象在从数据库检索到它被保存回来的时间之间发生了更改:创建一个新会话,再次加载原始对象,通知用户发生了冲突,并且显示他的更改以及数据库中当前的内容。他可以选择保存他的更改或取消(并接受现在存储在数据库中的内容)。
在违反约束的情况下,似乎会发生以下两种情况之一:
- 该对象在数据库中没有更改,可以将其重新加载到新会话中。
- 该对象在数据库中也已更改,不再反映最初加载的内容。
但是,我认为我实际上无法检测到发生了哪种情况,因为由于发生了约束异常(我已经对此进行了测试),所以永远不会抛出陈旧状态异常。
处理案例 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
{
// ????
}
}