Обработка нарушений ограничений в Winforms с использованием NHibernate - PullRequest
2 голосов
/ 23 февраля 2012

Я хотел бы обработать случай, когда пользователь редактирует объект из базы данных в приложении Windows Forms, выполняет редактирование, которое нарушает ограничение базы данных (то есть уникальное значение столбца), сохраняет объект обратно в базу данных,и NHibernate выдает исключение, таким образом разрушая сеанс.

Я использую руководство, опубликованное в статье MSDN Создание настольного приложения для работы с NHibernate и использование подхода «сессия на докладчика / форму».При создании докладчика я сохраняю ссылку на SessionFactory, создаю новый сеанс, извлекаю объект из базы данных через сеанс и сохраняю ссылку на него [объект].Когда форма отображается, ее поля заполняются из объекта.

Когда в форму вносятся изменения, и пользователь желает сохранить данные, объект обновляется из значений полей, и объект сохраняетсявернуться к базе данных.

Я обрабатываю исключения в устаревшем состоянии, когда объект изменяется между моментом, когда он был извлечен из базы данных, и когда он сохраняется обратно: создается новый сеанс, снова загружается исходный объект, пользователь информируетсячто конфликт произошел и представлен с его изменениями и что в настоящее время в базе данных.Он может сохранить свои изменения или отменить (и принять то, что сейчас хранится в базе данных).

В случае нарушения ограничения может возникнуть одна из двух вещей:

  1. Объект не изменился в базе данных, где его можно загрузить в новый сеанс.
  2. Объект также был изменен в базе данных и больше не отражает то, что было изначально загружено.

Тем не менее, я не думаю, что на самом деле могу определить, какой случай произошел, так как исключение устаревшего состояния никогда не генерируется, потому что произошло исключение ограничения (я проверял это).

Обработка случая 1 тривиальна, поскольку я могу просто отобразить сообщение об ошибке, в котором говорится, что «FIELD-X имеет значение, которое уже находится в БД», и сделать вид, что ничего плохого не произошло.Пользователь может изменить FIELD-X на уникальное значение и снова сохранить без необходимости повторного ввода своих изменений.

Обработка случая 2 будет аналогична обработке обычного исключения из устаревшего состояния.

Я знаю, что яможет "перебор", сохраняя копию оригинала (или его значения), а затем сравнивая два поля за полем. Однако я подозреваю, что есть лучший способ справиться с этим, используя NHibernate. Как бы вы справились с этим?

В случае, если это полезно:

  • Версия NHibernate: 3.2
  • Оптимистическая блокировка с использованием "грязной" стратегии
  • .NET Framework 2.0 SP2
  • База данных / драйвер 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 ]

1 голос
/ 06 марта 2012

Метод 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 ...");
}
0 голосов
/ 23 февраля 2012

Вы можете

// 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);
...