Как поймать ошибки БД и перевести их в значимую информацию для бизнес-уровня?
Обычно мне приходится вставлять некоторые данные в БД, и он не может быть вставлен, потому что в таблице есть ограничения, препятствующие этому. В приложении, которое я разрабатываю, некоторые бизнес-правила (например, "нет двух лиц с одинаковым типом и номером" или "XXXX-продукт уже зарегистрирован" ) выполняются с помощью UNIQUE или составных клавиш и других механизмов. Хотя я знаю, что СУБД выдает сообщение об ошибке (например, ORA-6346 или), я не знаю, как поймать эти ошибки в .net 4.0 и перевести их на ошибку, которая может иметь смысл для бизнес-уровня.
В качестве примера: я видел механизм вставки, который запрашивает БД, если регистр уже существует, а затем он вставляет данные, если это не так. Я хочу сделать это только с помощью запроса и поймать ошибку ограничения ограничений базы данных, потому что первый способ кажется мне очень неэффективным (DB может предупредить вас о дублировании с ошибкой).
Как я могу реализовать что-то подобное?
Примечание. Я думаю, что можно получить исключение из базы данных и использовать его код ORA-xxxx, чтобы попытаться выяснить, что произошло. Я не помню с точностью, если сообщение об ошибке показывает, какое ограничение (имя...) было нарушено, но код бизнес-уровня может содержать константы с именами ограничений и, из них, знать, что произошло.
Ответы
Ответ 1
То, что вы должны здесь делать, действительно зависит от архитектуры вашей системы и вашего отношения к размещению бизнес-логики.
Многие архитекторы систем предпочитают использовать базу данных в качестве немого хранилища данных и внедрять тип обработки ошибок и проверки целостности, о которых вы говорите, в среднем/прикладном уровне. Это совершенно правильный подход и особенно подходит для систем, требующих регулярных небольших выпусков, где бизнес-логика подвержена регулярным изменениям (гораздо проще перераспределить исполняемую среднюю неделю, чем согласовать выпуск базы данных), и где данные модель довольно проста.
Другой подход заключается в том, чтобы добавить определенную полупостоянную бизнес-логику в уровень базы данных. Это особенно эффективно, когда модель данных более сложна, и у вас есть хороший администратор баз данных!;)
Мое личное мнение заключается в том, что корпоративная база данных должна отвечать за ее собственную целостность, поэтому я предпочитаю иметь логику на уровне базы данных, чтобы обеспечить это - удаление любой уязвимости для ошибок, возникающих при выпуске кода без базы данных. Поэтому в вашем конкретном примере я определенно поймаю ошибку и расскажу об этом на вашем прикладном уровне.
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
Надеюсь, что это поможет.
Ответ 2
Есть несколько подходов, здесь в общем, что я буду делать:
- Позвольте ошибке перейти от обратного вызова БД к управляемому коду.
- Используйте компонент для проверки сообщения об ошибке, предоставленного SQL, и укажите соответствующее сообщение "пользовательский/бизнес-уровень".
Я согласен с Mellamokb в том, что обработка ошибок может быть выполнена в хранимом процессе, но это не соответствует вашему сценарию, поскольку вы специально хотите предоставить то, что понимает бизнес-уровень, - который по определению слой данных никогда не должен знать.
Для # 2 в библиотеках MS Enterprise есть блок обработки ошибок, который (я думаю) позволяет вам делать это через конфигурацию; или если это не поможет вам закрыть.
Ответ 3
Я думал об этом недавно. Я сделал метод расширения, который принимает сообщение от 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
, включенном в ваше исключение, для получения более надежных результатов, это было всего лишь доказательством концепции.
Ответ 4
Метод, который я знаю и использовал, - это выполнить те же самые проверки и вернуть полезные коды ошибок в ваше приложение. Например, если у вас есть хранимая процедура, которая вставляет запись в базу данных, она также должна проверить, будут ли все ограничения удовлетворены, а если нет, верните соответствующий код ошибки:
(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
Ответ 5
Если вы ищете вдохновение, посмотрите, как NHibernate обрабатывает это с помощью интерфейса ISQLExceptionConverter
. Вы можете увидеть примерную реализацию здесь.