Как обращаться с нарушениями ограничений db в пользовательском интерфейсе?

Мы реализуем большинство наших бизнес-правил в базе данных, используя хранимые процедуры.

Я не могу решить, как лучше передать ошибки нарушения ограничений данных из базы данных обратно в пользовательский интерфейс. Ограничения, о которых я говорю, связаны больше с бизнес-правилами, чем с целостностью данных.

Например, ошибка db, такая как "Невозможно вставить повторяющуюся строку ключа", совпадает с бизнес-правилом "у вас не может быть более одного Foo с тем же именем". Но мы "реализовали" его в наиболее распространенном смысле: как уникальное ограничение, которое генерирует исключение, когда правило нарушается.

Другие правила, такие как "Вам разрешено только 100 футов в день", не вызывают ошибок за один раз, поскольку они грациозно обрабатываются специальным кодом, например return empty dataset, который код приложения проверяет и передает обратно слой ui.

И в этом есть руб. Наш код ui выглядит так (это код веб-сервисов AJAX.NET, но любая инфраструктура ajax будет делать):

WebService.AddFoo("foo", onComplete, onError); // ajax call to web service

function onComplete(newFooId) {
    if(!newFooId) {
        alert('You reached your max number of Foos for the day')
        return
    }
    // update ui as normal here
}

function onError(e) {
    if(e.get_message().indexOf('duplicate key')) {
        alert('A Foo with that name already exists');
        return;
    }
    // REAL error handling code here
}

(В качестве примечания: Я замечаю, что это то, что делает stackoverflow, когда вы отправляете комментарии слишком быстро: сервер генерирует ответ HTTP 500, и ui его ловит.)

Итак, вы видите, что мы обрабатываем нарушения бизнес-правил в двух местах здесь, один из которых (то есть уникальная ошибка constaint) обрабатывается как особый случай для кода, который должен обрабатывать реальные ошибки (а не нарушения бизнес-правил), так как .NET распространяет Исключения полностью до обработчика onError().

Это кажется неправильным. Мои варианты, я думаю:

  • поймайте исключение "дублирующее ключевое нарушение" на уровне сервера приложений и преобразуйте его во все, что ожидает ui, как флаг "бизнес-правила нарушен" ,
  • вытеснить ошибку (например, с помощью "select name from Foo where name = @Name") и вернуть все, что сервер приложений ожидает, поскольку флаг "бизнес-правила нарушен" ,
  • в том же балласте, что и 2): используйте уникальное ограничение, встроенное в слой db, и слепо insert into Foo, перехватывая любые исключения и преобразовывая его во все, что сервер приложений ожидает в качестве флага "бизнес-правила нарушен"
  • вслепую insert into Foo (например, 3) и пусть это исключение распространяется на ui, плюс сервер приложений поднимает нарушения бизнес-правил как реальные Exceptions (в отличие от 1). Таким образом, ВСЕ ошибки обрабатываются в слое ui layer onError() (или аналогичном).

То, что мне нравится в 2) и 3), заключается в том, что нарушения бизнес-правил "брошены" там, где они реализованы: в сохраненной proc. Что мне не нравится в 1) и 3), я думаю, что они включают в себя глупые проверки, такие как "if error.IndexOf('duplicate key')", как и то, что сейчас находится в слое ui.

Изменить: мне нравится 4), но большинство людей говорят, что используйте Exception только в исключительных обстоятельствах.

Итак, как вы справляетесь с нарушением правил бизнес-правил до ui элегантно?

Ответ 1

Хранимая процедура может использовать оператор RAISERROR для возврата информации об ошибке вызывающему. Это можно использовать таким образом, чтобы пользовательский интерфейс мог решить, как будет отображаться ошибка, позволяя хранимой процедуре предоставить сведения об ошибке.

RAISERROR можно вызвать с помощью msg_id, серьезностью и состоянием и с набором аргументов ошибки. При использовании этого способа сообщение с данным msg_id должно быть введено в базу данных с использованием хранимой процедуры системы sp_addmessage. Этот msg_id может быть получен как свойство ErrorNumber в SqlException, которое будет вызвано в коде .NET, вызывающем хранимую процедуру. Затем пользовательский интерфейс может решить, какое сообщение или другое сообщение отображать.

Аргументы ошибки заменяются в результирующем сообщении об ошибке аналогично тому, как оператор printf работает на C. Однако, если вы хотите просто передать аргументы обратно в пользовательский интерфейс, чтобы пользовательский интерфейс мог решить, как их использовать, просто сделайте сообщения об ошибках не имеют текста, просто заполнители для аргументов. Одно сообщение может быть "% s" |% d ", чтобы передать строковый аргумент (в кавычках) и числовой аргумент. Код .NET мог бы разделить их на части и использовать их в пользовательском интерфейсе, как вам нравится.

RAISERROR также может использоваться в блоке TRY CATCH в хранимой процедуре. Это позволит вам уловить дублируемую ключевую ошибку и заменить ее собственным номером ошибки, что означает "дублировать ключ при вставке" в ваш код и может включать в себя фактическое значение ключа. Пользовательский интерфейс может использовать это для отображения "Номер заказа уже существует", где "x" - это ключевое значение.

Ответ 2

Мы не выполняем нашу бизнес-логику в базе данных, но у нас есть все наши серверы на стороне проверки, при этом операции низкого уровня DB CRUD отделены от бизнес-логики более высокого уровня и кода контроллера.

То, что мы пытаемся сделать внутренне, - это передать объект проверки с такими функциями, как Validation.addError(message,[fieldname]). Различные уровни приложений добавляют результаты проверки на этот объект, а затем мы вызываем Validation.toJson() для получения результата, который выглядит следующим образом:

{
    success:false,
    general_message:"You have reached your max number of Foos for the day",
    errors:{
        last_name:"This field is required",
        mrn:"Either SSN or MRN must be entered",
        zipcode:"996852 is not in Bernalillo county. Only Bernalillo residents are eligible"
    }
}

Это можно легко обработать на стороне клиента, чтобы отображать сообщения, относящиеся к отдельным полям, а также общие сообщения.

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

Ответ 3

Проблема на самом деле является одним из ограничений в архитектуре вашей системы. Путем нажатия всей логики в базу данных вам нужно обрабатывать ее в двух местах (в отличие от создания уровня бизнес-логики, который связывает пользовательский интерфейс с базой данных. Затем снова, когда у вас есть уровень бизнес-логики, вы теряете все выгоды от наличия логики в хранимых процедурах. Не защищая того или другого. Оба сосут примерно одинаково. Или не сосать. В зависимости от того, как вы на это смотрите.

Где я был? Правильно.

Я думаю, что комбинация из 2 и 3 - это, вероятно, путь.

Предотвращая ошибку, вы можете создать набор процедур, которые могут быть вызваны из UI-обращенного кода, чтобы обеспечить пользователю конкретную обратную связь для конкретной реализации. Вам необязательно делать это с помощью ajax в полевых условиях, но вы можете.

Уникальные ограничения и другие правила, которые находятся в базе данных, затем становятся последней проверкой работоспособности для всех данных и могут предполагать, что данные хороши перед отправкой, и выбрасывают Исключения как само собой разумеющееся (предпосылка заключается в том, что эти процедуры всегда должны вызываться с достоверными данными, и поэтому недопустимые данные являются исключительным обстоятельством).

Ответ 4

В защиту №4 SQL Server имеет довольно упорядоченную иерархию уровней серьезности ошибок, предопределенных. Поскольку, поскольку вы хорошо указываете на ошибки, когда логика есть, я бы склонен обрабатывать это по соглашению между SP и абстракцией пользовательского интерфейса, вместо того, чтобы добавлять кучу дополнительной связи. Тем более, что вы можете поднимать ошибки как со значением, так и с строкой.

Ответ 5

Я видел много приложений на основе Ajax, которые в реальном времени проверяют такие поля, как имя пользователя (чтобы узнать, существует ли он), как только пользователь покинет окно редактирования. Мне кажется, что это лучший подход, чем уход в базу данных для создания исключения, основанного на ограничении db - он более активен, поскольку у вас есть реальный процесс: получить значение, проверить, действительно ли оно, показать ошибку, если нет, разрешить продолжить, если нет ошибки. Таким образом, вариант 2 является хорошим.

Ответ 6

Вот как я поступаю, хотя это может быть не лучшим для вас:

Обычно я использую упреждающую модель, хотя это сильно зависит от архитектуры приложения.

Для меня (в моей среде) имеет смысл проверять большинство ошибок в среднем (бизнес-объектах) уровне. Здесь происходит вся другая бизнес-специфическая логика, поэтому я стараюсь сохранить и остальную часть моей логики здесь. Я думаю, что база данных как-то сохранится для моих объектов.

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

Бизнес-правила (например, "у вас может быть только столько foos в день" ), проверяются в серверном коде на уровне бизнес-объекта.

В базу данных проверяются только правила данных (ссылочная целостность, уникальные ограничения полей и т.д.). Мы также проверяем их все на среднем уровне, чтобы избежать ненужного использования базы данных.

Таким образом, моя база данных защищает себя от простых, ориентированных на данные правил, которые она хорошо оборудовала для обработки; более переменные бизнес-ориентированные правила живут на земле объектов, а не на земле записей.