«Правильный» способ проверки параметров хранимой процедуры - PullRequest
30 голосов
/ 29 июня 2009

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

Мой первый подход к проверке ошибок выглядел так:

create proc spBaz
(
  @fooInt int = 0,
  @fooString varchar(10) = null,
  @barInt int = 0,
  @barString varchar(10) = null
)
as
begin
  if (@fooInt = 0 and (@fooString is null or @fooString = ''))
    raiserror('invalid parameter: foo', 18, 0)

  if (@barInt = 0 and (@barString is null or @barString = ''))
    raiserror('invalid parameter: bar', 18, 0)

  print 'validation succeeded'
  -- do some work
end

Это не сработало, поскольку уровень серьезности 18 не останавливает выполнение, и вместе с сообщениями об ошибках выводится «проверка прошла успешно».

Я знаю, что могу просто добавить возврат после каждого рейзеррора, но мне это кажется некрасивым:

  if (@fooInt = 0 and (@fooString is null or @fooString = ''))
  begin
    raiserror('invalid parameter: foo', 18, 0)
    return
  end

  ...

  print 'validation succeeded'
  -- do some work

Поскольку ошибки со степенью серьезности 11 и выше обнаруживаются в блоке try / catch, другой проверенный мною подход заключался в инкапсуляции проверки ошибок внутри такого блока try / catch. Проблема заключалась в том, что ошибка была проглочена и не отправлена ​​клиенту вообще. Поэтому я провел небольшое исследование и нашел способ отбросить ошибку:

  begin try
    if (@fooInt = 0 and (@fooString is null or @fooString = ''))
      raiserror('invalid parameter: foo', 18, 0)

    ...
  end try
  begin catch
    exec usp_RethrowError
    return
  end catch

  print 'validation succeeded'
  -- do some work

Я все еще не доволен этим подходом, поэтому я прошу вас:

Как выглядит проверка вашего параметра? Есть ли какая-то «лучшая практика» для такого рода проверки?

Ответы [ 5 ]

45 голосов
/ 29 июня 2009

Я не думаю, что есть единственный "правильный" способ сделать это.

Мои собственные настройки будут аналогичны вашему второму примеру, но с отдельным шагом проверки для каждого параметра и более явными сообщениями об ошибках.

Как вы говорите, это немного громоздко и безобразно, но смысл кода очевиден для любого, кто его читает, и он выполняет свою работу.

IF (ISNULL(@fooInt, 0) = 0)
BEGIN
    RAISERROR('Invalid parameter: @fooInt cannot be NULL or zero', 18, 0)
    RETURN
END

IF (ISNULL(@fooString, '') = '')
BEGIN
    RAISERROR('Invalid parameter: @fooString cannot be NULL or empty', 18, 0)
    RETURN
END
1 голос
/ 20 апреля 2011

Как вы можете видеть из этой истории ответов, я проследил за этим вопросом и принял ответ, а затем приступил к «изобретению» решения, которое было в основном таким же, как ваш второй подход.

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

Поэтому, для записи, я предпочитаю ваш второй подход: использовать SP, чтобы вызвать текущую ошибку, а затем использовать TRY / CATCH для проверки вашего параметра.

Это уменьшает потребность во всех блоках IF / BEGIN / END и, следовательно, уменьшает количество строк, а также возвращает фокус на валидацию. При чтении кода для SP важно иметь возможность видеть тесты, выполняемые для параметров; все дополнительные синтаксические ошибки, удовлетворяющие синтаксическому анализатору SQL, просто мешают, на мой взгляд.

1 голос
/ 29 июня 2009

Обычно мы избегаем поднятия метода размещения () и возвращаем значение, которое указывает на ошибку, например, отрицательное число:

if <errorcondition>
    return -1

Или передать результат в двух выходных параметрах:

create procedure dbo.TestProc
    ....
    @result int output,
    @errormessage varchar(256) output
as
set @result = -99
set @errormessage = null
....
if <errorcondition>
    begin
    set @result = -1
    set @errormessage = 'Condition failed'
    return @result
    end
0 голосов
/ 20 апреля 2011

Я всегда использую параметр @Is_Success bit как OUTPUT. Так что, если у меня есть ошибка, то @ Is_success = 0. Когда родительская процедура проверяет, что @ Is_Success = 0, она откатывает свою транзакцию (с дочерними транзакциями) и отправляет сообщение об ошибке из @Error_Message клиенту.

0 голосов
/ 29 июня 2009

Я предпочитаю возвращаться как можно скорее, и не вижу смысла возвращать все из той же точки в конце процедуры. Я приобрел эту привычку делать сборку много лет назад. Кроме того, я всегда возвращаю значение:

RETURN 10

Приложение отобразит фатальную ошибку на положительных числах и выведет предупреждение пользователя на отрицательные значения.

Мы всегда передаем обратно параметр OUTPUT с текстом сообщения об ошибке.

пример:

IF ~error~
BEGIN
    --if it is possible to be within a transaction, so any error logging is not ROLLBACK later
    IF XACT_STATE()!=0
    BEGIN
        ROLLBACK
    END

    SET @OutputErrMsg='your message here!!'
    INSERT INTO ErrorLog (....) VALUES (.... @OutputErrMsg)
    RETURN 10

END
...