Преобразование SqlException в пользовательский обработчик исключений - PullRequest
3 голосов
/ 12 октября 2010

У меня есть приложение, которое использует хранимые процедуры MS SQL для обеспечения соблюдения некоторых бизнес-правил. Когда ошибка обнаружена, она вызывается как исключение с помощью RAISERROR обратно в мое приложение .Net.

Приложение .Net может затем использовать блоки Try / Catch для перехвата и исключения, а также выполнения и бизнес-логики. Проблема в том, что в одной хранимой процедуре проверено несколько бизнес-правил. Который может поднять разные исключения. Каков наилучший способ перехвата этих исключений SQL и преобразования их в пользовательские обработчики исключений .Net.

Например, моя хранимая процедура может выдать исключение для RuleA и RuleB. В моем коде .Net я могу только захватить SqlException. Мое пользовательское сообщение об ошибке для RuleA или RuleB возвращается во внутреннем исключении SqlException. Я мог бы разобрать строку сообщения, но это ужасно, и если кто-то изменит реализацию в хранимой процедуре. моя логика не подхватит это.

Каков предпочтительный метод для перевода общего SqlException в MyRuleAException или MyRuleBException?

Ответы [ 3 ]

3 голосов
/ 12 октября 2010

Обычно способ сделать это - определить константы ошибок в вашем .Net-коде, а затем вы можете проверить значение в вашем коде обработки исключений. Вы можете использовать константы, чтобы сделать код более читабельным, примерно так:

/// <summary>
/// Represents the error code returned from stored procedure when entity could not be found.
/// </summary>
private const int SQL_ERROR_CODE_ENTITY_NOT_FOUND = 50001;

/// <summary>
/// Represents the error code returned from stored procedure when entity to be updated has time mismatch.
/// </summary>
private const int SQL_ERROR_CODE_TIME_MISMATCH = 50002;

/// <summary>
/// Represents the error code returned from stored procedure when a persistence exception occurs (ex.
/// billing flag is invalid, child records exist which prevent a delete, etc.).
/// </summary>
private const int SQL_ERROR_CODE_PERSISTENCE_ERROR = 50003;

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

    if (e.InnerException is SqlException)
    {
        // verify exception code from SP and throw proper exception if required
        var sqlException = (SqlException)e.InnerException;
        if (sqlException.Number == SQL_ERROR_CODE_ENTITY_NOT_FOUND)
        {
            e = new EntityNotFoundException(e.Message, e);
        }
        else if (sqlException.Number == SQL_ERROR_CODE_TIME_MISMATCH)
        {
            e = new EntityTimestampMismatchException(e.Message, e);
        }
        else if (sqlException.Number == SQL_ERROR_CODE_PERSISTENCE_ERROR)
        {
            e = new EntityServicePersistenceException(e.Message, e);
        }
    }

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

И чтобы поднять ошибку, вы можете сделать что-то подобное в T-SQL:

 -- record wasn't found, raise an error
 DECLARE @l_error NVARCHAR(1000)
 SET @l_error = 'Record with ' + @p_IdFieldName + ' = ' + CONVERT(VARCHAR(128), @p_id)
    + ' does not exist in table [' + @p_TableName + ']'
 EXEC sp_addmessage @msgnum=50001, @severity=16, @msgtext=@l_error, @replace='replace'
 RAISERROR(50001, 16, 1)

50001 представляет номер ошибки, который будет в SqlException.Number.

1 голос
/ 12 октября 2010

Я согласен с dcp.Этот процесс требует, чтобы вы сгенерировали список констант, и это довольно долгий процесс для реализации.Но будет очень прост в обслуживании.

0 голосов
/ 12 октября 2010

Можете ли вы указать msg_id при возникновении ошибки?Если так, то я считаю, что это можно найти в элементе SqlException.Number.Тогда вы можете сделать if / else для этого.Я бы просто тщательно документировал это в сохраненном процессе.

ОБНОВЛЕНИЕ:

При ближайшем рассмотрении я думаю, что вам лучше было бы указать различные уровни ошибок, когда вывызовите RAISERROR, а затем проверьте этот уровень с помощью члена SqlException.Class.Пример:

--Rule A
RAISERROR (N'Rule A violation.', -- Message text.
           10, -- Severity,
           1, -- State)

--Rule B
RAISERROR (N'Rule B violation.', -- Message text.
           9, -- Severity,
           1, -- State)

--Rule C
RAISERROR (N'Rule C violation.', -- Message text.
           8, -- Severity,
           1, -- State)

затем в коде:

catch(SqlException qex)
{
  if(qex.Class == 10){}
  else if(qex.Class == 9){}
  else if(qex.Class == 8){}
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...