Перехват конкретного исключения

#c# #exception-handling #refactoring

#c# #исключение #рефакторинг

Вопрос:

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

Не удается вставить дублирующуюся ключевую строку в object ‘dbo.BillIdentity’ с уникальным индексом ‘IX_BillIdentity’. Выполнение инструкции было завершено.

Как я могу перехватить это исключение?
В настоящее время я проверяю, используя этот код :

  catch (Exception ex) {
    if (ex.Message.Contains("Cannot insert duplicate key row in object 'dbo._BillIdentity' with unique index 'IX__BillIdentity")) {
        string ScriptKey = "$(function() {ShowMessage('Error');});";
        ScriptManager.RegisterStartupScript(Page, GetType(), "script", ScriptKey, true);
    }
}
  

Я думаю, что это плохой код запаха.
Есть ли способ получше?

Комментарии:

1. Почему бы вам не проверить, существует ли ключ, прежде чем пытаться запустить инструкцию insert?

2. @Johann Blais, каковы преимущества этой работы?

3. Избегание дрянного обходного пути на основе исключений, о котором вы просите 😉

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

Ответ №1:

Обработайте SqlException только в этом случае.

[Править]

Для проверки исключения дублирующегося ключа в MS SQL server:

 try
{
    // try to insert
}
catch (SqlException exception)
{
    if (exception.Number == 2601) // Cannot insert duplicate key row in object error
    {
        // handle duplicate key error
        return;                  
    }
    else
        throw; // throw exception if this exception is unexpected
}
  

Редактировать:
Откуда взялся 2601?

 select *
from sys.messages
where text like 'Cannot insert duplicate key%'
  

ВОЗВРАТ:

 message_id  language_id severity is_event_logged text
----------- ----------- -------- --------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2601        1033        14       0               Cannot insert duplicate key row in object '%.*ls' with unique index '%.*ls'. The duplicate key value is %ls.
  

Используя exception.Number и ссылаясь на sys.messages view, вы можете обработать любое конкретное исключение MS SQL.

Комментарии:

1. привет, Алекс, отличный ответ. Именно то, что я ищу, за исключением того, что это для другого исключения. Есть ли ссылки на эти номера исключений?

2. @LuukKrijnen — вы можете запросить sys.messages эти коды ошибок.

3. @AlexAza Могу ли я таким образом определить, в каком столбце произошло нарушение?… У меня может быть больше уникальных ключей в одной таблице, и я хочу перехватить каждый из них отдельно

4. Для моего приложения это было 2627 . Использование Server 2014. Также я использую Linq для SQL, возможно, это объясняет разницу в количестве.

Ответ №2:

Вы не указали тип генерируемого исключения, но вы можете перехватить этот конкретный тип исключения. Например:

 catch (DuplicateKeyException e) {
    ...
}
  

Возможно, что для только этой ошибки не будет определенного типа исключения, но если вам нужно перехватить что-то довольно общее, например, SqlException , вы можете затем поискать более подробную информацию в самом классе. Например, в SqlException есть Errors свойство, в котором вы можете просмотреть более подробную информацию о каждой из (возможно, нескольких) ошибок на стороне базы данных. У каждого SqlError тогда есть Number свойство, которое будет указывать тип ошибки. Вы всегда можете вернуться к сообщению, если вам абсолютно необходимо, но тогда вам нужно знать о возможности изменения сообщения для разных культур и т.д.

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

 catch (SqlException e) {
    if (CheckWeCanHandle(e)) {
        // Mess with the ScriptManager or whatever
    } else {
        throw;
    }
}
  

Комментарии:

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

2. @Shaaah, если ты не можешь повторно отбросить, тогда зачем спрашивать о перехвате конкретного исключения? Кажется, вы хотите обработать это по-другому.

3. @shaahin: Вероятно, вам следует повторно удалить все ошибки, которые вы на самом деле не можете обработать, а затем создать консолидированную часть уровня представления для обработки ошибок, чтобы предоставить соответствующую страницу с ошибкой.

4. @JonSkeet: Могу ли я таким образом определить, в каком столбце произошло нарушение?… У меня может быть больше уникальных ключей в одной таблице, и я хочу перехватить каждый из них отдельно

Ответ №3:

Я только что взял проект, в котором кто-то пошел по этому маршруту:

 Catch ex As SqlException
    Select Case ex.Number
            Case 2601
                ...
  

Обратите внимание на следующее (из sys.messages в SQL Server):

2601 — Не удается вставить дублирующуюся строку ключа в объект ‘%.*ls’ с уникальным индексом ‘%.*ls’.

Но как насчет этого ..?

2627 — Нарушение ограничения %ls ‘%.*ls’. Не удается вставить дубликат ключа в объект ‘%.*ls’.»

Я только что потратил некоторое время на отслеживание именно этой проблемы.

А что, если мы сменим поставщика БД? Предположительно, 2601 не является абсолютно универсальным… Это отвратительно, ИМО. И если вы имеете (были) дело с этим на вашем уровне представления, я думаю, есть вопросы поважнее, которые нужно задать.

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

В настоящее время я склоняюсь к тому, чтобы сделать легкий выбор для идентификатора в открытом соединении и вообще избежать исключения.

Комментарии:

1. Хорошо продуманный ответ. Мне это нравится.

Ответ №4:

Как описано здесь, вы можете использовать фильтры исключений. Пример:

 try
{ /* your code here */ }
catch (SqlException sqlex) when (sqlex.Number == 2627)
{ /* handle the exception */ }
  

Ответ №5:

Рабочий код для исключения нарушения первичного ключа, дублирующего только фильтр

 using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;

try {
    abc...
} catch (DbUpdateException ex) {
    if (ex.InnerException.InnerException is SqlException sqlEx amp;amp; sqlEx.Number == 2601) {
        return ex.ToString();
    } else {
        throw;
    }
}
  

Обратите внимание на мелкие детали: — ex.InnerException.InnerException не ex.InnerException

Ответ №6:

Вы могли бы перехватить SQLException только для начала

 catch (SqlException ex) {
    if (ex.Message.Contains("Cannot insert duplicate key row in object 'dbo._BillIdentity' with unique index 'IX__BillIdentity")) {
        string ScriptKey = "$(function() {ShowMessage('Error');});";
        ScriptManager.RegisterStartupScript(Page, GetType(), "script", ScriptKey, true);
    }
}