Обработка нарушений ограничений в Winforms с использованием NHibernate
Я хотел бы обработать случай, когда пользователь редактирует объект из базы данных в приложении Windows Forms, выполняет редактирование, которое нарушает ограничение базы данных (то есть уникальное значение столбца), сохраняет объект обратно в базу данных и выбрасывает NHibernate исключение, таким образом разрушающее сессию.
Я использую руководство, опубликованное в статье MSDN Построение приложения To-Do для рабочего стола с помощью NHibernate, и использую подход "сессия на докладчика / форму". При создании докладчика я сохраняю ссылку на SessionFactory, создаю новый сеанс, извлекаю объект из базы данных через сеанс и сохраняю ссылку на него [объект]. Когда форма отображается, ее поля заполняются от объекта.
Когда в форму вносятся изменения, и пользователь желает сохранить данные, объект обновляется из значений полей, и объект сохраняется обратно в базу данных.
Я обрабатываю исключения устаревшего состояния, когда объект изменяется между моментом, когда он был извлечен из базы данных, и когда он сохраняется обратно: создается новый сеанс, снова загружается исходный объект, пользователю сообщается, что возник конфликт, и представлен с его изменениями и что в настоящее время в базе данных. Он может сохранить свои изменения или отменить их (и принять то, что сейчас хранится в базе данных).
В случае нарушения ограничения может показаться, что может произойти одно из двух:
- Объект не изменился в базе данных, где он может быть перезагружен в новый сеанс.
- Объект также был изменен в базе данных и больше не отражает то, что было первоначально загружено.
Тем не менее, я не думаю, что на самом деле могу определить, какой случай произошел, так как исключение устаревшего состояния никогда не генерируется, потому что произошло исключение ограничения (я проверял это).
Обработка случая 1 тривиальна, поскольку я могу просто отобразить сообщение об ошибке, которое говорит, что "FIELD-X имеет значение, которое уже находится в БД", и сделать вид, что ничего действительно плохого не произошло. Пользователь может изменить FIELD-X на уникальное значение и снова сохранить без необходимости повторного ввода своих изменений.
Обработка случая 2 будет аналогична обработке обычного исключения из состояния ожидания.
Я знаю, что могу "грубой силой" это сохранить копию оригинала (или его значения), а затем сравнить два поля за полем. Тем не менее, я подозреваю, что есть лучший способ справиться с этим, используя NHibernate. Как бы вы справились с этим?
В случае, если это полезно:
- NHibernate Версия: 3.2
- Оптимистическая блокировка с использованием "грязной" стратегии
- .NET Framework 2.0 с пакетом обновления 2
- База данных / драйвер SQLite
РЕДАКТИРОВАТЬ 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 ответа
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 ...");
}
Вы могли бы
// 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
}
если вас не интересует, что находится в БД
// 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);