FIBPlus: Обработка ошибок базы данных
Еще одним из несомненных достоинств FIBPlus являются средства обработки ошибок базы данных. В данной статье мы рассмотрим эти средства.
Основной компонент, используемый в обработке ошибок, — TpFibErrorHandler. Он позволяет централизованно обрабатывать ошибки базы данных.
Подготовка к обработке ошибок
Для иллюстрации возьмем базу данных FIBSAMPLE.GDB, используемую в большинстве примеров по FIBPlus. В нашем примере мы будем использовать две таблицы из этой базы данных: TREFCOUNTRY и PERSON. Они имеют следующее объявление:
CREATE TABLE TREFCOUNTRY (
NAME DNAME30,
FULLNAME DNAME60,
CODCTR DCODCTR NOT NULL,
CAPITAL DNAME30,
REGION DNAME30,
DESCRIPTION DDESCR
);
CREATE TABLE PERSON (
CODPERS INTEGER NOT NULL,
FIRST_NAME DNAME20,
LAST_NAME DNAME20,
COUNTRY DCODCTR
);
ALTER TABLE PERSON ADD CONSTRAINT "PERS CHECK LASTNAME NOTNULL"
check (last_name is not null);
ALTER TABLE PERSON ADD CONSTRAINT "PERS CHECK LASTNAME VALUE"
check (last_name not containing '***');
ALTER TABLE PERSON ADD CONSTRAINT "PERS PRIMARYKEY"
PRIMARY KEY (CODPERS);
ALTER TABLE TREFCOUNTRY ADD CONSTRAINT "Country PRIMARY KEY"
PRIMARY KEY (CODCTR);
ALTER TABLE PERSON ADD CONSTRAINT "PERS FOREIGN KEY"
FOREIGN KEY (COUNTRY) REFERENCES TREFCOUNTRY (CODCTR);
Создайте в Delphi или C++Builder новый проект ErrorHandler. Положите на форму следующие компоненты:
StatusBar1: TStatusBar;
Panel1: TPanel;
Panel2: TPanel;
Splitter1: TSplitter;
Splitter2: TSplitter;
Memo1: TMemo;
DBGrid1: TDBGrid;
DBGrid2: TDBGrid;
BExit: TSpeedButton;
BRefresh: TSpeedButton;
BSRollback: TButton;
BSCommit: TButton;
Database1: TpFIBDatabase;
WriteTransaction: TpFIBTransaction;
CountryData: TpFIBDataSet;
DSCountry: TDataSource;
PersData: TpFIBDataSet;
DSPerson: TDataSource;
ErrorHandler1: TpFibErrorHandler;
Примечание. Поскольку вы используете компонент TpFibErrorHandler, вы должны явно указать модуль fib в uses для Delphi:
Свойство DBName компонента Database1 ссылается на базу данных FIBSAMPLE.GDB. Компонент CountryData типа TpFIBDataSet ссылается на таблицу TREFCOUNTRY. Компонент PersData ссылается на таблицу PERSON. Используя SQL Generator, сгенерируйте обычным образом все операторы SQL для этих компонентов.
Компонент DSCountry свяжите с CountryData. Свойство DataSource у DBGrid1 установите в DSCountry. Компонент DBGrid1 отображает содержимое набора данных CountryData. Аналогичным образом свяжите компонент DSPerson с PersData. Свойство DataSource у DBGrid2 установите в DSPerson. Компонент DBGrid2 отображает содержимое PersData.
В поле Memo1 будет отображаться информация об ошибках.
Рис. 1. Проект ErrorHandler. Обработка ошибок базы данных
Напишите следующие обработчики событий щелчка по кнопкам Rollback и Commit:
procedure TFormMain.BSRollbackClick(Sender: TObject);
begin
WriteTransaction.RollbackRetaining;
CountryData.FullRefresh;
StatusBar1.Panels.Items[1].Text :=
IntToStr(CountryData.RecordCount);
DBGrid1.SetFocus;
end;
procedure TFormMain.BSCommitClick(Sender: TObject);
begin
WriteTransaction.CommitRetaining;
CountryData.FullRefresh;
PersData.FullRefresh;
DBGrid1.SetFocus;
end;
Напишите следующий обработчик события щелчка по кнопке Refresh:
procedure TFormMain.BRefreshClick(Sender: TObject);
begin
CountryData.FullRefresh;
PersData.FullRefresh;
StatusBar1.Panels.Items[1].Text :=
IntToStr(CountryData.RecordCount);
end;
Все эти обработчики понадобятся только для того, чтобы подтверждать или откатывать транзакцию, а также для обновления содержимого наборов данных.
Свойства и особенности компонента TpFIBErrorHandler
Центральная часть этой программы — компонент ErrorHandler1 и обработчик его события OnFIBEventError.
Установите в True все подсвойства свойства Options этого компонента: oeException, oeForeignKey, oeLostConnect, oeCheck, oeUniqueViolation. Это позволит перехватывать и обрабатывать в программе все основные типы исключений при работе с базой данных.
Рис. 2. Характеристики компонента ErrorHandler1.
Компонент TpFibErrorHandler также содержит следующие свойства (только для чтения):
Таблица 1. Свойства только для чтения компонента TpFibErrorHandler.
Свойство |
Значение |
ConstraintName |
Имя ограничения, вызвавшего ошибку. |
ExceptionNumber |
Номер пользовательского исключения (exception), вызвавшего ошибку. Если ошибка вызвана не исключением, то значение будет –1. |
LastError |
Тип последнего исключения — объект класса TKindIBError: |
Следующая таблица содержит описание значения подсвойств свойства Options.
Таблица 2. Список подсвойств свойства Options компонента TpFibErrorHandler.
Подсвойство |
Значение |
oeException |
Обрабатываются пользовательские исключения. Текст ошибки не выводится, номер пользовательского исключения передается в свойстве компонента ExceptionNumber. |
oeForeignKey |
Нарушение значения внешнего ключа (foreign key). |
oeLostConnect |
Потеря связи с базой данных. |
oeCheck |
Нарушение ограничения CHECK. |
oeUniqueViolation |
Нарушение ограничения UNIQUE. |
Вернемся к основной части нашей программы. Напишите следующий обработчик события OnFIBEventError для компонента ErrorHandler1:
procedure TFormMain.ErrorHandler1FIBErrorEvent(Sender: TObject;
ErrorValue: EFIBError; KindIBError: TKindIBError;
var DoRaise: Boolean);
var Lasterror: string;
FKindIBError: string;
begin
Memo1.Lines.Add(#13#10 + '===== ErrorHandler FIBErrorEvent =====');
Memo1.Lines.Add('Sender.ClassName = ' + Sender.ClassName);
Memo1.Lines.Add('Sender.Name = ' + (Sender as TComponent).Name);
if Sender is TFIBQuery then
Memo1.Lines.Add('Owner.Name = ' +
(Sender as TFIBQuery).Owner.Name);
if Sender is TpFIBStoredProc then
Memo1.Lines.Add('Sender.StoredProcName = ' +
(Sender as TpFIBStoredProc).StoredProcName);
Memo1.Lines.Add('ConstraintName = ' +
ErrorHandler1.ConstraintName);
Memo1.Lines.Add('ExceptionNumber = ' +
IntToStr(ErrorHandler1.ExceptionNumber));
case ErrorHandler1.LastError of
keNoError: Lasterror := 'keNoError';
keException: Lasterror := 'keException';
keForeignKey: Lasterror := 'keForeignKey';
keSecurity: Lasterror := 'keSecurity';
keLostConnect: Lasterror := 'keLostConnect';
keCheck: Lasterror := 'keCheck';
keUniqueViolation: Lasterror := 'keUniqueViolation';
keOther: Lasterror := 'keOther';
else
Lasterror := 'Undefined';
end;
Memo1.Lines.Add('Lasterror = ' + Lasterror);
Memo1.Lines.Add('SQLCode = ' + IntToStr(ErrorValue.SQLCode));
Memo1.Lines.Add('IBErrorCode = ' +
IntToStr(ErrorValue.IBErrorCode));
Memo1.Lines.Add('Message = ' + ErrorValue.Message);
Memo1.Lines.Add('IBMessage = ' + ErrorValue.IBMessage);
Memo1.Lines.Add('SQLMessage = ' + ErrorValue.SQLMessage);
case KindIBError of
keNoError: FKindIBError := 'keNoError';
keException: FKindIBError := 'keException';
keForeignKey: FKindIBError := 'keForeignKey';
keSecurity: FKindIBError := 'keSecurity';
keLostConnect: Lasterror := 'keLostConnect';
keCheck: FKindIBError := 'keCheck';
keUniqueViolation: FKindIBError := 'keUniqueViolation';
keOther: FKindIBError := 'keOther';
else
FKindIBError := 'Undefined';
end;
Memo1.Lines.Add('KindIBError = ' + FKindIBError);
// DoRaise := False;
end;
Обработчику передаются следующие параметры: 1. ErrorValue — объект класса EFIBError. Класс содержит следующие свойства:
1.IBErrorCode — содержит код ошибки InterBase. Является наиболее информативным описателем ошибки. Существует около 400 различных кодов. Список кодов приведен в документе InterBase Language Reference в 5 главе Error Codes and Messages (таб).
- IBMessage — содержит текст сообщения об ошибке.
- SQLCode — содержит код ошибки SQLCODE.
- SQLMessage — содержит сообщение об ошибке SQL.
2. KindIBError — объект класса TKindIBError. Может иметь значения, описанные в таблице 1, в свойстве LastError.
3. DoRaise — переменная логического типа. Позволяет указать, следует ли после обработки ошибки в данном обработчике вызывать повторно исключение. Если DoRaise присваивается значение True (по умолчанию), то после выполнения действий в обработчике ошибок будет вызвано стандартное исключение с выдачей соответствующих сообщений. Если же DoRaise имеет значение False, то действие, вызвавшее ошибку, отменяется, никаких сообщений не выдается. В нашем обработчике ошибок базы данных вся возможная информация об ошибке — с использованием свойств компонента TpFibErrorHandler и параметров, передаваемых в процедуру, — выводится в поле Memo1.
Внимание. Компонент TpFibErrorHandler позволяет обрабатывать множество ошибок базы данных. При этом ошибки подключения к базе данных в нем не обрабатываются (не путать с ошибкой при потере подключения и попытках возобновления подключения). Для этого следует использовать стандартные средства Delphi или C++Builder — блок try…except (Delphi) или try…catch (C++Builder). Не обрабатываются также такие ситуации, когда для компонента, работающего с базой данных (DataSet, Query, SoredProc и некоторые другие), не указана база данных или транзакция.
Пример перехвата ошибки подключения к базе данных.
try
Database1.Connected := True;
except
ShowMessage(’Ошибки при подключении к базе данных’);
Application.Terminate;
end;
Обработка исключений
Теперь мы можем посмотреть, как работает наш обработчик ошибок.
Запустите программу на выполнение. Удалите значение первичного ключа (CODCTR) в любой строке в таблице TREFCOUNTRY (левый DBGrid). Это приводит к тому, что значение поля становится NULL, что недопустимо для первичного ключа. В поле Memo1 появится следующий текст:
========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TFIBQuery
Sender.Name = UpdateQuery
Owner.Name = CountryData
ConstraintName =
ExceptionNumber = -1
Lasterror = keOther
SQLCode = -625
IBErrorCode = 335544347
Message = FormMain.CountryData.UpdateQuery:
The insert failed because a column definition includes validation constraints.validation error
for column CODCTR, value "*** null ***".
IBMessage = validation error for column CODCTR, value "*** null ***".
SQLMessage = The insert failed because a column definition includes validation constraints.
KindIBError = keOther
Обратите внимание на первые три строчки сообщения после заголовка:
Sender.ClassName = TFIBQuery
Sender.Name = UpdateQuery
Owner.Name = CountryData
Здесь Sender.ClassName содержит имя класса объекта, вызвавшего исключение (TFIBQuery). Sender.Name — имя объекта (UpdateQuery), Owner.Name — имя владельца объекта: имя компонента TpFIBDataSet (CountryData). Если мы в этот же набор данных попытаемся добавить новую запись с пустым значением первичного ключа, то получим такое же сообщение, только значением Sender.Name будет InsertQuery.
Аналогичный результат будет получен, если вы попытаетесь установить в NULL значение первичного ключа (CODPERS) таблицы PERSON.
Попытка ввести дублированное значение первичного ключа в любую строку в таблице PERSON приводит к выдаче сообщения:
========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TFIBQuery
Sender.Name = UpdateQuery
Owner.Name = PersData
ConstraintName = PERS
ExceptionNumber = -1
Lasterror = keUniqueViolation
SQLCode = -803
IBErrorCode = 335544665
Message = violation of PRIMARY or UNIQUE KEY constraint "PERS PRIMARYKEY" on table "PERSON".
IBMessage = violation of PRIMARY or UNIQUE KEY constraint "PERS PRIMARYKEY" on table "PERSON".
SQLMessage = Invalid insert or update value(s): object columns are
constrained - no 2 table rows can have duplicate column values.
KindIBError = keUniqueViolation
Аналогичным образом можно проверить реакцию на нарушение других ограничений базы данных. Проверим результат обработки ошибки «потеря соединения». Для моделирования потери соединения завершите выполнение сервера InterBase/Firebird. Если запущена программа Guardian, следует завершить ее до завершения сервера. После этого нужно щелкнуть по кнопке Refresh. Это вызовет желаемую ошибку. В результате обработчик выдаст следующие сообщения:
========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TFIBQuery
Sender.Name = RefreshQuery
Owner.Name = CountryData
ConstraintName =
ExceptionNumber = -1
Lasterror = keLostConnect
SQLCode = -901
IBErrorCode = 335544741
Message = FormMain.CountryData.RefreshQuery:
Unsuccessful execution caused by system error that does not preclude successful execution of
subsequent statements.connection lost to database.
IBMessage = connection lost to database.
SQLMessage = Unsuccessful execution caused by system error that does not preclude successful
execution of subsequent statements.
KindIBError =
Обратите внимание на значения SQLCode и IBErrorCode. SQLCode = -901. Если посмотреть значения SQLCode и IBErrorCode в документации по InterBase (Language Reference), то можно увидеть, что этому значению соответствует ровно 60 вариантов ошибок. Значение же IBErrorCode = 335544741 имеет только один тип ошибки: «connection lost to database», то есть, потеря соединения с базой данных. Еще раз следует подчеркнуть, что с целью обработки ошибок базы данных в программе наиболее полезным является именно параметр IBErrorCode, передаваемый обработчику ошибок.
Отдельно стоит посмотреть, как работает обработчик ошибок при вызове из хранимой процедуры или из триггера пользовательского исключения. Нам нужно добавить в нашу базу данных FIBSAMPLE.GDB исключение и простенькую хранимую процедуру, вызывающую это исключение.
Создать исключение можно следующим образом, используя файл скрипта:
CONNECT 'D:\Указываем конкретный путь к базе данных\FIBSAMPLE.GDB'
USER 'SYSDBA' PASSWORD 'masterkey';
CREATE EXCEPTION EXCEPT1 'Exception 1';
commit;
Аналогично создается хранимая процедура после создания исключения:
CONNECT 'D:\ Указываем конкретный путь к базе данных\FIBSAMPLE.GDB'
USER 'SYSDBA' PASSWORD 'masterkey';
CREATE PROCEDURE EXEEXCEPT1
AS
begin
EXCEPTION EXCEPT1;
end;
commit;
Для вызова исключения нужно также внести изменения в наш проект. Положите на форму кнопку с именем Exception и компонент TpFIBStoredProc. Для компонента TpFIBStoredProc задайте следующие характеристики:
Рис. 3. Характеристики компонента TpFIBStoredProc обращения к хранимой процедуре
Напишите следующий обработчик щелчка по кнопке Exception:
procedure TFormMain.ExceptionClick(Sender: Tobject);
begin
FIBStoredProc1.ExecProc;
end;
Запустите программу на выполнение, щелкните по кнопке Exception. В поле Memo1 отобразится следующее:
========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TpFIBStoredProc
Sender.Name = FIBStoredProc1
Sender.StoredProcName = EXEEXCEPT1
ConstraintName =
ExceptionNumber = 1
Lasterror = keException
SQLCode = -836
IBErrorCode = 335544517
Message = Exception 1.
IBMessage = exception 1.
Exception 1.
SQLMessage = exception 268785020.
KindIBError = keException
Здесь Sender.ClassName имеет значение (TpFIBStoredProc), поскольку исключение мы получили при вызове хранимой процедуры. Sender.Name содержит имя компонента, через который была вызвана хранимая процедура (FIBStoredProc1). Sender.StoredProcName содержит имя хранимой процедуры.
Здесь следует напомнить, что пользовательское исключение влияет только на программу, вызвавшую это исключение через хранимую процедуру или триггер. Другим приложениям оно не передается.
Последний пример на конфликт одновременного изменения одной и той же записи разными клиентами. Запустите два экземпляра программы. Это будут два разных клиента. Измените любую запись в одном процессе и ту же запись в другом процессе. При переходе на следующую запись в DBGrid (при этом для изменяемой записи выдается Post) будет вызвано исключение:
========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TFIBQuery
Sender.Name = UpdateQuery
Owner.Name = CountryData
ConstraintName =
ExceptionNumber = -1
Lasterror = keOther
SQLCode = -901
IBErrorCode = 335544345
Message = FormMain.CountryData.UpdateQuery:
Unsuccessful execution caused by system error that does not preclude successful execution of
subsequent statements.lock conflict on no wait transaction.
deadlock.
update conflicts with concurrent update.
IBMessage = lock conflict on no wait transaction.
deadlock.
update conflicts with concurrent update.
SQLMessage = Unsuccessful execution caused by system error that does not preclude successful
execution of subsequent statements.
KindIBError = keOther
Заключение
Мы рассмотрели механизм использования TpFIBErrorHandler и продемонстрировали перехват всех основных видов ошибок, которые могут возникнуть при работе приложения с реальной базой данных. Способ и смысл обработки этих исключительных ситуаций зависят от программы, которую вы разрабатываете, а FIBPlus предлагает вам достаточно гибкий и удобный инструмент для перехвата и анализа.
Перехват ошибок SQL на Delphi в ADOConnection
Перехват ошибок SQL на Delphi при работе с компонентом ADO
Механизм, как «запретить — разрешить» выводить сообщение об ошибке подключения программы на Delphi через SQL через компонент TADOConnection.
Типичная обработка ошибки заключается в том, чтобы обрамить блоки кода в конструкции try..except или try..finally. Однако, многие попросту не делают этого, так как им недосуг или обработка исключений оставляется «на потом».
Чтобы не выводить сообщения об ошибке можно написать такой код, используя конструкцию «try-finally»:
procedure TAnswerList.Update(ADO : TADOQuery); begin with ADO do begin if Active then Close; SQL.Text:=concat('UPDATE ANSWER_LIST SET ', 'NAME = "', self.Name, ', ', 'NOMER_ID = "', IntToStr(self.Nomer), ', ', 'CREDITOR_ID = "', IntToStr(self.CreditorId), ' ', 'WHERE ID = ' + IntToStr(self.id)); try ExecSQL; finally end; end; end;
Чтобы грамотно вывести сообщение об ошибке можно использовать другую конструкцию:
procedure TAnswerList.Update(ADO : TADOQuery); begin with ADO do begin if Active then Close; SQL.Text:=concat('UPDATE ANSWER_LIST SET ', 'NAME = "', self.Name, ', ', 'NOMER_ID = "', IntToStr(self.Nomer), ', ', 'CREDITOR_ID = "', IntToStr(self.CreditorId), ' ', 'WHERE ID = ' + IntToStr(self.id)); try ExecSQL; except On E:Exception Do begin ShowMessage(concat('Update answer list failure. ',e.Message,#13#10,SQL.Text)); Exit; end; end; end;
Как вариант, Вы можете использовать свойство Application.OnException, которое является глобальным обработчиком событий приложения.
Для этого предлагается оформить обработку исключений в отдельный компонент.
unit sExceptionsCatcher; {***} interface {***} uses Classes, SysUtils, JPEG; type TsExceptionsCatcher = class(TComponent) private FEnabled: boolean; FGenerateScreenshot: boolean; FJPEGScreenshot: boolean; FJpegQuality: TJPEGQualityRange; FCollectInfo: boolean; Fn: TFilename; procedure SetEnabled(const Value: boolean); { Private declarations } protected { Protected declarations } procedure EnableCatcher; procedure DisableCatcher; public { Public declarations } constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Catcher(Sender: TObject; E: Exception); published { Published declarations } property Enabled: boolean read FEnabled write SetEnabled default False; end; procedure Register; {***} implementation {***} uses Windows, Forms, Dialogs, Graphics; procedure Register; begin RegisterComponents(\'Catch Exceptions\', [TsExceptionsCatcher]); end; { TsExceptionsCatcher } constructor TsExceptionsCatcher.Create(AOwner: TComponent); begin inherited; end; destructor TsExceptionsCatcher.Destroy; begin DisableCatcher; inherited; end; procedure TsExceptionsCatcher.SetEnabled(const Value: boolean); begin FEnabled := Value; if Enabled then EnableCatcher else DisableCatcher; end; procedure TsExceptionsCatcher.DisableCatcher; begin Application.OnException := nil; end; procedure TsExceptionsCatcher.EnableCatcher; begin Application.OnException := Catcher; end; procedure TsExceptionsCatcher.Catcher(Sender: TObject; E: Exception); begin {TODO: Write some exception handling code} end; end.
Реализация функций
// скриншот активного окна procedure TsExceptionsCatcher.DoGenerateScreenshot; var bmp: TBitmap; jpg: TJPEGImage; begin bmp := Screen.ActiveForm.GetFormImage; begin jpg := TJPEGImage.Create; jpg.CompressionQuality := 100; jpg.Assign(bmp); jpg.SaveToFile(fn+\'.jpg\'); FreeAndNil(jpg); end; FreeAndNil(bmp); end;
// отчет, в котором будут указаны имя компьютера и имя текущего пользователя function TsExceptionsCatcher.CollectUserName: string; var uname: pchar; unsiz: cardinal; begin uname := StrAlloc(255); unsiz := 254; GetUserName(uname,unsiz); if (unsiz > 0) then Result := string(uname) else Result := \'n/a\'; StrDispose(uname); end; function TsExceptionsCatcher.CollectComputerName: string; var cname: pchar; cnsiz: cardinal; begin cname := StrAlloc(MAX_COMPUTERNAME_LENGTH + 1); cnsiz := MAX_COMPUTERNAME_LENGTH + 1; GetComputerName(cname,cnsiz); if (cnsiz > 0) then Result := string(cname) else Result := \'n/a\'; StrDispose(cname); end;
// Процедура создает объект класса TStringList, наполняет его информацией, которую надо записать и сохраняет в файл, // с именем, аналогичном имени файла со скриншотом procedure TsExceptionsCatcher.DoCollectInfo(E: Exception); var sl: TStringList; begin sl := tstringlist.Create; sl.add(\'--- This report is created by automated \'+ \'reporting system.\'); sl.add(\'Computer name is: [\'+ComputerName+\']\'); sl.add(\'User name is: [\'+UserName+\']\'); sl.add(\'--- End of report ----------------------\'+ \'-----------------\'); sl.SaveToFile(Fn+\'.txt\'); end;
Дата публикации: 2015-05-12 10:36:28
Данная статья посвящена обработке ошибок и основана на официальной документации.
-старая версия
-новая версия
Введение
Класс EFDDBEngineException является базовым для всех ошибок DBMS. Экземпляр класса представляет собой коллекцию ошибок БД, доступных через свойство EFDDBEngineException.Errors[], а также представленных классом TFDDBError.
FireDAC подразумевает персонализацию и унификацию EFDDBEngineException исключений TFDDBError ошибок. “Персонализация” означает, что драйвер может иметь свои собственные исключения и классы ошибок, которые содержат информацию, специфичную для DBMS. Далее, в документации идет такая таблица
TFDDBError имеет свойство ErrorCode, которое содержит нативные коды ошибок DBMS
Унификация означает, что все классы исключения унаследованы от EFDDBEngineException – базового класса, который содержит независимую информацию о драйверах. Свойство этого класса Kind это независимая ошибка DBMS. В качестве примера, обработка ошибки уникальности ключа может быть следующей.
try FDQuery1.ExecSQL(‘insert into MyTab(code, name) values (:code, :name)’, [100, ‘Berlin’]); except on E: EFDDBEngineException do begin if E.Kind = ekUKViolated then ShowMessage(‘Please enter unique value !’); raise; end; end; |
В справке указаны следующие TFDCommandExceptionKind
FireDAC.Stan.Error.TFDCommandExceptionKind
TFDCommandExceptionKind = (ekOther, ekNoDataFound, ekTooManyRows, ekRecordLocked, ekUKViolated, ekFKViolated, ekObjNotExists, ekUserPwdInvalid, ekUserPwdExpired, ekUserPwdWillExpire, ekCmdAborted, ekServerGone, ekServerOutput, ekArrExecMalfunc, ekInvalidParams); |
Информация об ошибках содержится в следующих свойствах класса EFDDBEngineException
- Errors — коллекция TFDDBError объектов.
- ErrorCount — Число ошибок коллекции.
- Kind — Независимые от DBMS ошибки.
- Message — актуальное сообщение ошибки.
А также в свойствах TFDDBError
- ErrorCode — Специфические ошибки DBMS.
- Kind — Общие для всех DBMS ошибки.
- Message — Сообщения об ошибках
Техника обработки ошибок
Собственно обработка ошибок может осуществляться несколькими способами
1) Традиционно try / except / end
FDConnection1.StartsTransaction; try FDQuery1.ExecSQL; FDConnection1.Commit; except on E: EFDDBEngineException do begin FDConnection1.Rollback; // do something here raise; end; end; |
2) Через обработку события TFDQuery.OnError
3) Через обработку события TFDConnection.OnError
Предыдущие 2 способа хороши для “логирования” исключений или настройки исключений, например
Пример из документации
procedure TForm1.FDConnection1Error(ASender: TObject; const AInitiator: IFDStanObject; var AException: Exception); var oExc: EFDDBEngineException; begin if AException is EFDDBEngineException then begin oExc := EFDDBEngineException(AException); if oExc.Kind = ekRecordLocked then oExc.Message := ‘Please, try the operation later. At moment, the record is busy’ else if (oExc.Kind = ekUKViolated) and SameText(oExc[0].ObjName, ‘UniqueKey_Orders’) then oExc.Message := ‘Please, provide the unique order information. It seems, your order was already put’; end; end; FDConnection1.OnError := FDConnection1Error; |
4) Через обработку TFDQuery.OnExecuteError для специфических ошибок Array DML
5) Через обработку TFDQuery.OnUpdateError для обработки ошибок при инструкции Update
6) Через обработку событий TFDConnection.OnLost, OnRestored, OnRecover для обработки ошибок потери соединения.
GUI для конечного пользователя
Чтобы воспользоваться этой возможностью, нужно сделать следующее – добавить на форму TFDGUIxErrorDialog,
Диалог ловит событие TApplication.OnException и выдает всплывающее сообщение. Вкладка Query позволяет увидеть SQL команду, которая привела к ошибке. Ctrl+C копирует информацию об ошибке в буфер.
This entry was posted in Delphi, FireDAC. Bookmark the permalink.
Go Up to Working with Connections (FireDAC)
This topic describes how to handle database errors with FireDAC.
Contents
- 1 General
- 2 Error Information
- 3 Handling Exceptions
- 4 Using End User Error Dialog
- 5 See Also
General
The EFDDBEngineException class is the base class for all DBMS exceptions. A single exception object is a collection of database errors, accessible through the EFDDBEngineException.Errors[] property and represented by the TFDDBError class.
FireDAC combines «personalization» and unification of the EFDDBEngineException exception and of the TFDDBError error classes. «Personalization» means that a driver can have its own exception and error classes, which contain information specific to the DBMS:
DBMS | Exception class | Error class |
---|---|---|
Advantage Database | FireDAC.Phys.ADSWrapper.EADSNativeException | FireDAC.Stan.Error.TFDDBError |
DataSnap server | FireDAC.Phys.TDBXBase.ETDBXNativeException | FireDAC.Stan.Error.TFDDBError |
dbExpress v4 | FireDAC.Phys.TDBXBase.ETDBXNativeException | FireDAC.Stan.Error.TFDDBError |
Firebird | FireDAC.Phys.IBWrapper.EIBNativeException | FireDAC.Phys.IBWrapper.TFDIBError |
IBM DB2 | FireDAC.Phys.DB2.EDB2NativeException | FireDAC.Phys.ODBCWrapper.TFDODBCNativeError |
Informix | FireDAC.Phys.Infx.EInfxNativeException | FireDAC.Phys.Infx.TFDInfxError |
InterBase | FireDAC.Phys.IBWrapper.EIBNativeException | FireDAC.Phys.IBWrapper.TFDIBError |
Microsoft Access | FireDAC.Phys.MSAcc.EMSAccessNativeException | FireDAC.Phys.ODBCWrapper.TFDODBCNativeError |
Microsoft SQL Server | FireDAC.Phys.MSSQL.EMSSQLNativeException | FireDAC.Phys.MSSQL.TFDMSSQLError |
MySQL | FireDAC.Phys.MySQLWrapper.EMySQLNativeException | FireDAC.Phys.MySQLWrapper.TFDMySQLError |
ODBC | FireDAC.Phys.ODBCWrapper.EODBCNativeException | FireDAC.Phys.ODBCWrapper.TFDODBCNativeError |
Oracle | FireDAC.Phys.OracleWrapper.EOCINativeException | FireDAC.Phys.OracleWrapper.TOCIError |
PostgreSQL | FireDAC.Phys.PGWrapper.EPgNativeException | FireDAC.Phys.PGWrapper.TFDPgError |
SQLite | FireDAC.Phys.SQLiteWrapper.ESQLiteNativeException | FireDAC.Stan.Error.TFDDBError |
Sybase SQL Anywhere | FireDAC.Phys.ASAWrapper.EASANativeException | FireDAC.Phys.ODBCWrapper.TFDODBCNativeError |
Teradata Database | FireDAC.Phys.TData.ETDataNativeException | FireDAC.Phys.ODBCWrapper.TFDODBCNativeError |
TFDDBError has an ErrorCode property, which is the native DBMS error code.
«Unification» means that all driver exception classes are inherited from the EFDDBEngineException — a single base class that contains driver independent information. Its Kind property is a DBMS independent error code. For example, the code for handling a unique key violation may be as follows:
try FDQuery1.ExecSQL('insert into MyTab(code, name) values (:code, :name)', [100, 'Tokyo']); except on E: EFDDBEngineException do begin if E.Kind = ekUKViolated then ShowMessage('Please enter unique value !'); raise; end; end;
Error Information
The error information is mainly present by the EFDDBEngineException properties:
- Errors — the collection of the TFDDBError objects.
- ErrorCount — the number of errors in the Errors collection.
- Kind — the DBMS independent error kind.
- Message — the actual error message.
And by the TFDDBError properties:
- ErrorCode — the DBMS vendor specific error code.
- Kind — the DBMS independent error kind.
- Message — the error message.
To simplify the application debugging or to make exception logging more informative, the EFDDBEngineException provides the SQL and Params properties.
Also, depending on the error area and the DBMS ability to provide the advanced error information, the following TFDDBError properties are useful:
- When a SQL parsing error is involved, then CommandTextOffset returns the offset in the SQL command text.
- When a constraint violation, a DB object alteration failure, or some other errors are involved, then the ObjName property returns a database object name.
- When an Array DML error is involved, then RowIndex returns the array row index, to which the error belongs.
Handling Exceptions
Exceptions can be processed in one of the following ways:
- using the try/except/end construction. This is a standard Delphi way to handle exceptions. For example:
FDConnection1.StartsTransaction; try FDQuery1.ExecSQL; FDConnection1.Commit; except on E: EFDDBEngineException do begin FDConnection1.Rollback; // do something here raise; end; end;
- setting the TFDQuery.OnError event handler.
- setting the TFDConnection.OnError event handler. These are good ways to handle exception logging or exception «adjusting». For example:
procedure TForm1.FDConnection1Error(ASender: TObject; const AInitiator: IFDStanObject; var AException: Exception); var oExc: EFDDBEngineException; begin if AException is EFDDBEngineException then begin oExc := EFDDBEngineException(AException); if oExc.Kind = ekRecordLocked then oExc.Message := 'Please, try the operation later. At moment, the record is busy' else if (oExc.Kind = ekUKViolated) and SameText(oExc[0].ObjName, 'UniqueKey_Orders') then oExc.Message := 'Please, provide the unique order information. It seems, your order was already put'; end; end; FDConnection1.OnError := FDConnection1Error;
- setting the TFDQuery.OnExecuteError event handler for handling Array DML specific errors.
- setting the TFDQuery.OnUpdateError event handler for handling updates posting errors.
- setting the TFDConnection.OnLost, OnRestored, OnRecover event handlers for handling connection lost errors.
Using End User Error Dialog
With the help of the TFDGUIxErrorDialog component, an end user can be notified about errors returned by the database:
To use the dialog, just drop the component somewhere on a form. The dialog hooks the TApplication.OnException event handler and pops up the dialog, when there is an unhandled FireDAC exception. The «Query» page allows you to see the SQL command text produced by the exception. Pressing Ctrl+C in the dialog puts the complete exception information into clipboard.
See Also
- Recovering Connection
- Executing Commands
- FireDAC.Stan.Error Namespace
- TFDCustomConnection.OnError Event
- TFDAdaptedDataSet.OnError Event
В Delphi для обработки ошибок при работе с базой данных используется блок try..except.
Пример:
try // код работы с базой данных except on E: Exception do begin // обработка исключения end; end;
В блок try помещается код, который может вызвать исключение при работе с базой данных. Если исключение произойдет, то управление передается в блок except. В этом блоке можно производить обработку исключения и предпринимать необходимые действия, например, выводить сообщения об ошибке или записывать информацию об ошибке в файл.
Вводите конкретную ошибку, которую вы хотите обработать в блоке except, используя конструкцию on E: Exception do. В приведенном выше примере используется общая ошибка Exception, но можно использовать более конкретные ошибки, например, EDatabaseError, EDBClient, EDBEngineError и другие.