Обработка ошибок баз данных delphi

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 будет отображаться информация об ошибках.

[Image]

Рис. 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. Это позволит перехватывать и обрабатывать в программе все основные типы исключений при работе с базой данных.

[Image]

Рис. 2. Характеристики компонента ErrorHandler1.

Компонент TpFibErrorHandler также содержит следующие свойства (только для чтения):

Таблица 1. Свойства только для чтения компонента TpFibErrorHandler.

Свойство

Значение

ConstraintName

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

ExceptionNumber

Номер пользовательского исключения (exception), вызвавшего ошибку. Если ошибка вызвана не исключением, то значение будет –1.

LastError

Тип последнего исключения — объект класса TKindIBError:
keNoError — ошибка отсутствует,
keException — обработано пользовательское исключение,
keForeignKey — обработано нарушение внешнего ключа,
keLostConnect — соединение с базой данных потеряно,
keSecurity — обработано нарушение полномочий пользователя,
keUniqueViolation — обработано нарушение уникального значения,
keCheck — обработано нарушение ограничения CHECK,
keOther — обработан иной тип ошибки.

Следующая таблица содержит описание значения подсвойств свойства 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 задайте следующие характеристики:

[Image]

Рис. 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. Далее, в документации идет такая таблица

18

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 для конечного пользователя

FireDACErrorPicture

Чтобы воспользоваться этой возможностью, нужно сделать следующее – добавить на форму  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:

FireDACErrorPicture.png

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 и другие.

Понравилась статья? Поделить с друзьями:
  • Обработка ошибок vbs
  • Обработка ошибок vba access
  • Обработка ошибок try catch php
  • Обратное распространение ошибки habr
  • Обработка ошибок sqlite python