Как отследить ошибки БД и перевести их в значимую информацию для бизнес-уровня? - PullRequest
13 голосов
/ 13 сентября 2011

Обычно мне нужно вставить некоторые данные в БД, и они не могут быть вставлены, потому что в таблице есть ограничения, не позволяющие мне сделать это.В приложении, которое я разрабатываю, некоторые бизнес-правила (например, «нет двух человек с одинаковым типом и номером идентификатора» или «продукт XXXX уже зарегистрирован») применяются с УНИКАЛЬНЫМИ или составными ключами и другими механизмами.Хотя я знаю, что СУБД выдает сообщение об ошибке (например, ORA-6346 или), я не знаю, как отловить эти ошибки в .net 4.0 и преобразовать их в ошибку, которая может иметь значение для бизнес-уровня.

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

Как я могу реализовать что-то подобное?

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

Ответы [ 5 ]

7 голосов
/ 16 сентября 2011

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

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

Другой подход - поместить некоторую четко определенную полупостоянную бизнес-логику на уровень базы данных.Это особенно полезно, когда модель данных более сложна, и у вас есть хороший администратор баз данных!;)

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

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

PROCEDURE test() AS
  b VARCHAR2;
BEGIN

  -- if the following row exists, then DUP_VAL_ON_INDEX will be thrown 
  -- (assuming there is a primary key constraint)        

  INSERT INTO table(a,b,c)
  VALUES(1,2,3);  

  -- if there is no matching record, NO_DATA_FOUND will be thrown

  SELECT a
  INTO b
  FROM TABLE
  WHERE c = 'blah';  

EXCEPTION   -- both types of exception can be caught and embellished 
  WHEN DUP_VAL_ON_INDEX THEN
    raise_application_error(-20570, 'Attempted to insert a duplicate value', TRUE);
  WHEN NO_DATA_FOUND THEN
    raise_application_error(-20571, 'No matching row in table for value:' || 'blah', TRUE);
  WHEN OTHERS THEN
  rollback
END test;

Вы можете найти больше информации здесь: http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/errors.htm

Надеюсь, это поможет ..

3 голосов
/ 13 сентября 2011

Есть несколько подходов, в общих чертах я бы сделал:

  1. Пусть ошибка поднимается из вызова БД обратно в ваш управляемый код.
  2. Используйте компонент дляизучите сообщение об ошибке, предоставляемое SQL, и определите соответствующее сообщение «дружественный пользователю / бизнес-уровень».

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

Для # 2 в библиотеках MS Enterprise есть блок обработки ошибок, который (я думаю,) позволяет делать подобные вещи через конфиг;или если нет, это может приблизить вас.

1 голос
/ 18 сентября 2011

Я недавно думал об одном и том же. Я создал метод расширения, который берет Сообщение из SqlException и переводит его в нечто более полезное для конечного пользователя, используя регулярные выражения для извлечения полезной информации из сообщения об ошибке и String.Format для помещения этой информации в новое сообщение. ,

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

Это сообщение об ошибке SQL:

Violation of UNIQUE KEY constraint 'uniq_ticket'. Cannot insert duplicate key in object 'dbo.TicketHeader'. The statement has been terminated.

Возвращает этот результат:

Save to table dbo.TicketHeader failed: Ticket number must be unique.

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

    public static class SqlExceptionExtension
    {
        private static readonly Dictionary<string, string> Messages;
        private static readonly Dictionary<string, string> Constraints;
        static SqlExceptionExtension()
        {
            Messages = new Dictionary<string, string> {{@"Violation of UNIQUE KEY constraint '(?<Constraint>.*)'. Cannot insert duplicate key in object '(.*)'. The statement has been terminated.", "Save to table {2} failed: {0}"}};
            Constraints = new Dictionary<string, string> { { "uniq_ticket", "Ticket number must be unique." } };
        }
        public static string BusinessLayerMessage(this Exception e)
        {
            foreach(var reg in Messages.Keys)
            {
                var match = Regex.Match(e.Message, reg);
                if (match.Success)
                {
                    string friendlyConstraint = "";
                    if (match.Groups["Constraint"] != null)
                    {
                        friendlyConstraint = Constraints[match.Groups["Constraint"].Value] ??
                                             match.Groups["Constraint"].Value;
                    }
                    var groups = match.Groups.Cast<Group>().Select(x => x.Value);
                    var strings = new [] {friendlyConstraint};
                    return String.Format(Messages[reg], strings.Concat(groups).ToArray());
                }
            }
            return "Unexpected Database error.";
        }
    }
}

Я полагаю, что вы можете сделать это на отдельных SqlError или OracleError s, включенных в ваше исключение, для более надежных результатов, это было просто доказательством концепции.

0 голосов
/ 22 сентября 2011

Если вы ищете вдохновение, посмотрите, как NHibernate справляется с этим с помощью интерфейса ISQLExceptionConverter. Вы можете увидеть пример реализации здесь .

0 голосов
/ 13 сентября 2011

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

(pseudo-sql)
function create-user
    @username, @password, @name, @email
as

if @username already exists return 1  --duplicate username
if @email already exists return 2   --duplicate email

insert user values (@username, @password, @name, @email)
return 0   -- success
...