Delphi для профессионалов

         

ГЛАВА 17


Технология dbExpress

Одной из проблем различных технологий доступа к данным, используемым в приложениях Delphi, является трудность распространения готовых приложений. Для BDE требуется отдельная установка, которая занимает порядка 15 Мбайт дискового пространства, а также специальная настройка псевдонимов (см. гл. 16). ADO предустановлена в операционной системе, но нуждается в настраиваемых провайдерах данных (см. гл. 19). При необходимости обновить версию ADO, дистрибутив вашего приложения "потяжелеет" более чем на 2 Мбайт.

Новая технология доступа к данным dbExpress обеспечивает взаимодействие приложения с серверами баз данных. Драйверы dbExpress используют для получения данных исключительно запросы SQL. При этом на клиентской стороне отсутствует кэширование данных, вследствие этого здесь применяются исключительно однонаправленные курсоры и отсутствует возможность прямого редактирования наборов данных.

 Примечание 

Проблема редактирования данных в dbExpress


может быть решена несколькими путями (см. ниже). Однако любые предлагаемые способы повышают затраты на программирование и снижают эффективность полученного кода.

Взамен этих (весьма существенных для построения полноценных приложений) неудобств разработчики получили легкий и быстрый механизм доступа к данным.

Для функционирования компонентов dbExpress необходим только один драйвер, который взаимодействует напрямую с клиентским программным обеспечением для выбранного сервера БД. В поставку входят драйверы для первых четырех из списка серверов баз данных:

 DB2;  InterBase; MySQL;  Oracle;  Microsoft SQL Server 2000.

Драйверы реализованы в виде динамических библиотек, а при необходимости могут быть прикомпилированы непосредственно к исполняемому файлу приложения. Поэтому проблема распространения совместно с приложением средств доступа к данным в случае с dbExpress снимается полностью. Естественно, на компьютере должно быть установлено клиентское ПО соответствующего SQL сервера.

Кроме того, технология dbExpress обеспечивает доступ к данным в кроссплатформенных приложениях для Windows и Linux, т. к. применяется и в Delphi и Kylix (см. гл. 4), а способы ее применения идентичны.

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

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

Компоненты dbExpress располагаются в Палитре компонентов на одноименной странице.

В этой главе рассматриваются следующие вопросы:

 настройка соединений с различными серверами баз данных, подключение драйверов и установка их параметров;  способы использования компонентов dbExpress для просмотра данных и создание пользовательского интерфейса приложений;  программная реализация редактирования данных;  работа с данными в режиме кэширования изменений и использование компонента TSimpleDataSet;  использование интерфейсов;  распространение приложений с интегрированной технологией dbExpress.

 

Драйверы доступа к данным


Технология dbExpress обеспечивает доступ к серверу баз данных при помощи драйвера, реализованного как динамическая библиотека. Для каждого сервера имеется своя динамическая библиотека.

Таблица 17.1. Драйверы dbExpress

Сервер БД

Драйвер

Клиентское ПО

DB2

Dbexpdb2.dll.

Db2cli.dll

InterBase

Dbexpint.dll

GDS32.DLL

Informix

Dbexpinf.dll

Isqlb09a.dll

Microsoft SQL Server 2000

Dbexpmss.dll

OLE DB

MySQL

Dbexpmys.dll

LIBMYSQL.DLL

Oracle

Dbexpora.dll

OCI.DLL

Перечисленные в табл. 17.1 файлы находятся в папке \Delphi7\Bin.

Для доступа к данным сервера драйвер должен быть установлен на компьютере клиента. Для доступа к данным драйвер взаимодействует с клиентским ПО сервера, которое также должно быть инсталлировано на клиентской стороне.

Стандартные настройки для каждого драйвера хранятся в файле \Borland Shared\DBExpress\dbxdrivers.ini.

 

Соединение с сервером баз данных


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

После переноса этого компонента в модуль данных или на форму необходимо выбрать тип сервера и настроить параметры соединения.

Свойство

property ConnectionName: string;

позволяет выбрать из выпадающего списка конкретное настроенное соединение. По умолчанию разработчику доступно по одному настроенному соединению для каждого сервера БД. После выбора соединения автоматически устанавливаются значения свойств:

 property DriverName: string;

определяет используемый драйвер; 

 property LibraryName: string;

задает динамическую библиотеку драйвера dbExpress;

 property VendorLib: string;

определяет динамическую библиотеку клиентского ПО сервера (табл. 17.1); 

property Params: TStrings;

список этого свойства содержит настройки для выбранного соединения.

При необходимости все перечисленные свойства можно установить дополнительно.

Разработчик может дополнять и изменять список настроенных соединений. Для этого используется специализированный Редактор соединений dbExpress Connections (рис. 17.1). Он открывается после двойного щелчка на компоненте TSQLConnection или выбора команды Edit Connection Properties из всплывающего меню компонента.

Рис. 17.1. Окно редактора настроенных соединений компонента TSQLConnection

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

Список соединений в левой части соответствует списку выбора свойства ConnectionName в Инспекторе объектов. Настройки соединения из правой части отображаютсяв свойствее Params.

Настроенные соединения и их параметры сохраняются в файле \Borland Shared\DBExpress\dbxconnections.ini.

Примечание

При установке Delphi 7 поверх Delphi 6 сведения о соединениях из старого файла dbxconnections.ini добавляются в новый.

Конкретные значения настроек соединений зависят от используемого сервера БД и требований приложения (табл. 17.2).

Таблица 17.2. Настройка соединения dbExpress

Параметр

Значение

Общие настройки

BlobSize

Задает ограничение на объем пакета данных для данных BLOB

DriverName

Имя драйвера

ErrorResourceFile

Файл сообщений об ошибках

LocaleCode

Код локализации, определяющий влияние национальных символов на сортировку данных

User Name

Имя пользователя

Password

Пароль

DB2

Database

Имя клиентского ядра

DB2 Translsolation

Уровень изоляции транзакций

Informix

HostName

Имя компьютера, на котором работает сервер Informix

Informix Translsolation

Уровень изоляции транзакции

Trim Char

Определяет, нужно ли удалять из строковых значений полей пробелы, дополняющие значение до полной строки, заданной размером поля данных

Interbase

CommitRetain

Задает поведение курсора по завершении транзакции. При значении True курсор обновляется, иначе — удаляется

Database

Имя файла базы данных (файл GDB)

InterBase Translsolation

Уровень изоляции транзакции

RoleName

Роль пользователя

SQLDialect

Используемый диалект SQL. Для IntreBase 5 возможно только одно значение -1

Trim Char

Определяет, нужно ли удалять из строковых значений полей пробелы, дополняющие значение до полной строки, заданной размером поля данных

WaitOnLocks

Разрешение на ожидание занятых ресурсов

Microsoft SQL Server 2000

Database

Имя базы данных

HostName

Имя компьютера, на котором работает сервер MS SQL Server 2000

MSSQL Translsolation

Уровень изоляции транзакции

OS Autenification

Использование учетной записи текущего пользователя операционной системы (домена или Active Directory) при доступе к ресурсам сервера

MySQL

Database

Имя базы данных

HostName

Имя компьютера, на котором работает сервер MySQL

Oracle

AutoCommit

Флаг завершения транзакции. Устанавливается только сервером

BlockingMode

Задает режим завершения запроса. При значении True соединение дожидается окончания запроса (синхронный режим), иначе — начинает выполнение следующего (асинхронный режим)

Database

Запись базы данных в файле TNSNames.ora

Multiple Transaction

Поддержка управлением несколькими транзакциями в одной сессии

Oracle Translsolation

Уровень изоляции транзакций

OS Autenification

Использование учетной записи текущего пользователя операционной системы (домена или Active Directory) при доступе к ресурсам сервера

Trim Char

Определяет, нужно ли удалять из строковых значений полей пробелы, дополняющие значение до полной  строки, заданной размером поля данных

После выыбора настроенного соединения или выбора типа сервера и настройки параметров соединения компонент TSQLConnection готов к работе.

Свойство

property Connected: Boolean;

открывает соединение с сервером при значении True. Аналогичную операцию выполняет метод

procedure Open;

После открытия соединения все компоненты dbExpress, инкапсулирующие наборы данных и связанные с открытым компонентом TSQLConnection, получают доступ к базе данных.

Соединение закрывается тем же свойством connected или методом

procedure Close;

При открытии и закрытии соединения разработчик может использовать обработчики событий

property BeforeConnect: TNotifyEvent; 

property AfterConnect: TNotifyEvent;

property BeforeDisconnect: TNotifyEvent; 

property AfterDisconnect: TNotifyEvent;

Например, на стороне клиента можно организовать проверку пользователя приложения:

procedure TForml.MyConnectionBeforeConnect(Sender: TObject);

begin

if MyConnection.Params.Values['User_Name']) <> DefaultUser then

begin

MessageDlg('Wrong user name', mtError, [mbOK], 0);

Abort; 

end; 

end;

Свойство

property LoginPrompt: Boolean;

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

О текущем состоянии соединения можно судить по значению свойства

TConnectionState = (csStateClosed, csStateOpen, csStateConnecting, csStateExecuting, csStateFetching, csStateDisconnecting);

 property ConnectionState: TConnectionState;

Параметры соединения можно настраивать на этапе разработки в Инспекторе объектов или Редакторе соединений (см. рис. 17.1). Также это можно сделать и непосредственно перед открытием соединения, используя свойство Params или метод

 procedure LoadParamsFromlniFile(AFileName : String = '');

который загружает заранее подготовленные параметры из INI-файла. Проверить успешность этой операции можно при помощи свойства

property Params Loaded: Boolean;

значение True которого сигнализирует об успехе загрузки.

procedure TForml.StartBtnClickfSender: TObject);

 begin

if MyConnection.Params.Values['DriverName'] = " then

MyConnection.LoadParamsFromlniFile

('c:\Temp\dbxalarmconnections.ini');

if MyConnection.ParamsLoaded then

try MyConnection.Open;

except MessageDlgt'Database connection error', mtError, [mbOK], 0);

end; 

end;

 

Управление наборами данных


Компонент TSQLConnection позволяет выполнять некоторые операции с подключенными наборами данных и следить за их состоянием.

Свойство

property DataSetCount: Integer;

возвращает число подключенных через данное соединение наборов данных.

Но это только активные наборы данных, переданные в связанные компоненты. Общее число выполняющихся в настоящий момент запросов возвращает свойство

property ActiveStatements: LongWord;

Если сервер БД установил для данного соединения максимальное число одновременно выполняющихся запросов, то оно доступно в свойстве

property MaxStmtsPerConn: LongWord;

Поэтому перед открытием набора данных можно выполнять следующий код, который повысит надежность приложения:

if MyQuery.SQLConnection.ActiveStatements <= MyQuery.SQLConnection.MaxStmtsPerConn

their MyQuery.Open

else MessageDlg ('Database connection is busy', mtWarning, [mbOK] , 0) ;

В случае возникновения непредвиденной ситуации все открытые через данное соединение наборы данных можно быстро закрыть методом

procedure CloseDataSets;

без разрыва соединения.

При необходимости компонент TSQLConnection может самостоятельно выполнять запросы SQL, не прибегая к помощи компонента TSQLQuery или TSQLDataSet. Для этого предназначена функция

function Execute(const SQL: string; Params: TParams; 

ResultSet:Pointer=nil): Integer;

Если запрос должен содержать параметры, то необходимо сначала создать объект — список параметров TParams и заполнить его. При этом, т. к. объект TParams еще не связан с конкретным запросом, важен порядок следования параметров, который должен совпадать в списке TParams и в тексте SQL.

Если запрос возвращает результат, метод автоматически создает объект типа TCustomSQLDataSet и возвращает указатель на него в параметр Resultset. Функция возвращает число обработанных запросом записей. Следующий фрагмент кода иллюстрирует применение функции Execute.

procedure TForml.SendBtnClick(Sender: TObject); 

var FParams: TParams;

FDataSet: TSQLDataSet; 

begin

FParams := TParams.Create;

 try

FParams.Items[0].Aslnteger := 1234; FParams.Items[1].AsInteger := 6751;

MyConnection.Execute('SELECT * FROM Orders WHERE OrderNo >= :Ord AND

EmpNo = :Emp', FParams, FDataSet);

if Assigned(FDataSet) then

with FDataSet do

begin

Open;

while Not EOF do

begin 

{...} 

Next ;

end;

Close;

 end;

finally

FParams.Free; 

end; 

end;

Если запрос не имеет настраиваемых параметров и не возвращает набор данных, можно использовать функцию

function ExecuteDirect(const SQL: string): LongWord;

которая возвращает О в случае успешного выполнения запроса или код ошибки.

Метод

procedure GetTableNames(List: TStrings; SystemTables: Boolean = False);

возвращает список таблиц базы данных. Параметр SystemTables позволяет включать в формируемый список List системные таблицы.

Метод GetTableNames дополнительно управляется свойством

TTableScope = (tsSynonym, tsSysTable, tsTable, tsView);

 TTableScopes = set of TTableScope; 

property TableScope: TTableScopes;

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

procedure GetFieldNames(const TableName: String; List: TStrings);

и список индексов при помощи метода

procedure GetlndexNames(const TableName: string; List: TStrings);

В обоих методах список возвращаемых значений содержится в параметре List.

Аналогичным образом метод

procedure GetProcedureNames(List: TStrings); 

возвращает список доступных хранимых процедур, а метод

procedure GetProcedureParams(ProcedureName: String; List: TList); 

определяет параметры отдельной процедуры.

 

Транзакции


Подобно своим аналогам в BDE и ADO компонент TSQLConnection поддерживает механизм транзакций и делает это сходным образом.

Начало, фиксацию и откат транзакции выполняют методы

procedure StartTransaction(TransDesc: TTransactionDesc);

 procedure Commit(TransDesc: TTransactionDesc); 

procedure Rollback(TransDesc: TTransactionDesc);

При этом запись TTransactionDesc возвращает параметры транзакции:

TTransIsolationLevel = (xilDIRTYREAD, xilREADCOMMITTED, xilREPEATABLEREAD, xilCUSTOM);

TTransactionDesc = packed record

TransactionID : LongWord;

GloballD : LongWord;

IsolationLevel : TTransIsolationLevel;

Customlsolation : LongWord;

end;

Запись содержит уникальный в рамках соединения идентификатор транзакции TransactionID И уровень изоляции Транзакции IsolationLevel. При уровне изоляции xilCustom определяется параметр Customlsolation. Идентификатор GiobaliD используется при работе с сервером Oracle.

Некоторые серверы БД не поддерживают транзакции, и для определения этого факта используется свойство

property TransactionsSupported: LongBool;

Если соединение уже находится в транзакции, свойству

property InTransaction: Boolean;

присваивается значение True. Поэтому, если сервер не поддерживает множественные транзакции, всегда полезно убедиться, что соединение не обслуживает начатую транзакцию:

var Translnfo: TTransactionDesc;

(...)

if Not MyConnection.InTransaction then

try

MyConnection.StartTransaction(Translnfo); {...}

MyConnection.Commit(Translnfo);

 except

MyConnection.Rollback(Translnfo); 

end;

 

Использование компонентов наборов данных


Набор компонентов dbExpress, инкапсулирующих набор данных, вполне обычен и сравним с аналогичными компонентами BDE, ADO, InterBase Express. Это компоненты TSQLDataSet, TSQLTable, TSQLQuery, TSQLStoredProc.

Примечание

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

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

Хотя общим предком всех рассматриваемых здесь компонентов является класс TDataSet (см. рис. 12.1), который обладает полным инструментарием для работы с набором данных, компоненты dbExpress используют только однонаправленные курсоры и не позволяют редактировать данные. Однонаправленные курсоры ограничивают навигацию по набору данных и обеспечивают перемещение только на следующую запись и возврат на первую. Также здесь недоступны любые операции, требующие буферизации данных — это поиск, фильтрация, синхронный просмотр.

Таким образом, для выключения ряда механизмов класса TDataSet понадобился еще один Промежуточный класс TCustomSQLDataSet.

Отображение данных при помощи компонентов со страницы Data Controls также ограничено. Нельзя использовать компоненты TDBGrid и TDBCtrlGrid, а в компоненте TDBNavigator не забудьте отключить кнопки возврата на одну позицию назад и перехода на последнюю запись. Также ничего хорошего не получится из попытки применить компоненты синхронного просмотра. Остальные компоненты можно использовать обычным способом (см. гл. 15).

Основные способы применения компонентов dbExpress остаются стандартными и подробно описаны в части III.

Доступ к данным во всех рассматриваемых компонентах осуществляется одинаково — через соединение, инкапсулированное компонентом TSQLConnection. Привязывание к соединению выполняет свойство

property SQLConnection: TSQLConnection;

Рассмотрим теперь компоненты dbExpress подробнее.

Класс TCustomSQLDataSet


Так как общим предком компонентов dbExpress объявлен класс TDataSet, то задачей класса TCustomSQLDataSet является не столько внесение новой

функциональности, сколько корректное ограничение возможностей, заложенных в TDataSet. Непосредственно в приложениях этот класс не используется, но информация о нем полезна для понимания других компонентов dbExpress и для создания собственных компонентов на его основе.

Класс TCustomSQLDataSet является общим предком для компонентов, инкапсулирующих запросы, таблицы и хранимые процедуры. Для их поддержки используются свойства:

 TSQLCommandType = (ctQuery, ctTable, ctStoredProc);   property CommandType: TSQLCommandType;

определяющее тип команды, направляемой серверу;

 property CoramandText: string;

содержащее текст команды.

Если серверу передается запрос SQL (CommandType = ctQuery), свойство CoramandText содержит текст запроса. Если это команда на получение таблицы, свойство CommandText содержит имя таблицы, а далее с использованием имени таблицы создается запрос SQL на получение всех полей этой таблицы. Если необходимо выполнить процедуру, свойство CommandText содержит имя этой процедуры.

Текст команды, которая реально передается на сервер для выполнения, содержится в защищенном свойстве

property NativeCommand: string;

Для использования в табличном представлении существует свойство

property SortFieldNames: string;

определяющее порядок сортировки записей табличного набора данных. Свойство должно содержать список полей, разделенных точкой с запятой. Это свойство используется для создания выражения ORDER BY для генерируемой команды.

Для обработки исключительных ситуаций в классах — потомках может быть использовано защищенное свойство

property LastError: string;

которое возвращает текст последней ошибки dbExpress.

Для ускорения работы набора данных можно отключить получение от сервера метаданных об объекте запроса (таблицы, процедуры, полей, индексов), которые обычно направляются клиенту вместе с результатом запроса. Для этого свойству

property NoMetadata: Boolean;

присваивается значение True.

Однако пользоваться им нужно осторожно, т. к. для некоторых видов команд метаданные необходимы (это операции с использованием индексов).

Разработчик может управлять процессом получения метаданных. Для этого необходимо заполнить структуру

TSchemaType = (stNoSchema, stTables, stSysTables, stProcedures,

 stColumns, stProcedureParams, stIndexes);

 TSchemalnfo = record

EType : TSchemaType; 

ObjectName : String; 

Pattern : String;

 end;

которая доступна через защищенное свойство

property Schemalnfo: TSQLSchemalnfo;

а значит, может использоваться только при создании новых компонентов на основе TCustomSQLDataSet.

Параметр FTуре определяет тип требуемой информации. Параметр ObjectName — имя таблицы или хранимой процедуры, если в параметре FType указаны поля, индексы или параметры процедур.

Внимание

Если компонент должен получать результирующий набор данных, параметр FType должен обязательно иметь значение stNoSchema. При изменении значения свойства CommandText это условие выполняется автоматически.

Параметр Pattern определяет, какие ограничения накладываются на метаданные. Он содержит символьную маску, подобную свойству Mask многих визуальных компонентов. Последовательность символов маски обозначается символом %, единичный символ определяется символом .

При необходимости использовать управляющие символы в качестве маскирующих, применяются двойные символы %% и _.

Подобно свойству Tag класса TComponent, класс TCustomSQLDataSet имеет строковое свойство

property DesignerData: string

в котором разработчик может хранить любую служебную информацию. По существу, это просто лишняя строковая переменная, которую нет необходимости объявлять.

 

Компонент TSQLDataSet


Компонент TSQLDataSet является универсальным и позволяет выполнять запросы SQL (подобно TSQLQuery), просматривать таблицы целиком (подобно TSQLTable)или выполнять хранимые процедуры (подобно TSQLStoredProc).

Для определения режима работы компонента используется свойство ConimandType (см. выше).

Если ему присвоить значение ctTable, в списке свойства commandText можно выбрать имя таблицы, если конечно компонент подключен к соединению. При выборе значения ctQuery в свойстве CommandText необходимо определить текст запроса SQL. Для работы в режиме хранимой процедуры для свойства commandType используется значение ctstoredProc, а в списке свойства CommandText можно выбрать нужную процедуру.

Для открытия набора данных используются традиционные способы: свойство Active или метод Open. Если же запрос SQL или хранимая процедура не возвращают набор данных, для их выполнения используется метод

function ExecSQL(ExecDirect: Boolean = False): Integer; override;

Параметр ExecDirect определяет, необходимо ли произвести подготовку параметров перед выполнением команды. Если параметры запроса или процедуры существуют, параметр ExecDirect должен иметь значение False.

Дополнительно для табличного режима можно использовать свойство SortFieldNames (см. выше), определяющее порядок сортировки записей таблицы.

В режиме запросов и хранимых процедур для задания параметров используются свойства Params И ParamCheck (см. часть III).

Информация об используемых в результирующем наборе данных индексах сохраняется в свойстве

property IndexDefs: TIndexDefs;

 

Компонент TSQLTable


Компонент TSQLTable предназначен для просмотра таблиц целиком и по основным функциям подобен своим аналогам TTаblе, TADOтаblе,TIBTаblе (подробнее о функциях компонентов таблиц см. часть III).

Для получения табличного набора данных компонент TSQLTable самостоятельно формирует запрос на сервер, используя для этого возможности, унаследованные от предка TCustomSQLDataSet.

Метод

procedure PrepareStatement; override;

генерирует для выбранной таблицы текст запроса, который формируется компонентом для передачи на сервер.

Для определения имени таблицы используется свойство TableName, и, если компонент подключен к соединению, имя таблицы можно выбрать из списка.

Для подключения простых или составных индексов используются свойства

IndexFieldNames, IndexFields, IndexName. 

А метод

procedure GetlndexNames(List: TStrings);

возвращает в параметр List список используемых индексов.

Связь между двумя наборами данных главный/подчиненный организуется свойствами MasterFields, MasterSource.

Компонент TSQLTable предоставляет разработчику некоторое подобие функций редактирования. Для удаления всех записей из связанной с компонентом таблицы на сервере используется метод

procedure DeleteRecords;

 

Компонент TSQLQuery


Компонент TSQLQuery повторяет функциональность своих аналогов в BDE, ADO, InterBase Express и позволяет выполнять на сервере запросы SQL клиента. Подробнее о функциях компонентов запросов SQL см. часть III.

Текст запроса содержится в свойстве

property SQL: TStrings;

а его простое строковое представление в свойстве

property Text: string;

Если запрос возвращает набор данных, его выполнение осуществляется свойством Active или методом open. В противном случае используется метод

function ExecSQL(ExecDirect: Boolean = False): Integer; override;

Параметр ExecDirect = False означает, что запрос не имеет настраиваемых параметров.

 

Компонент TSQLStoredProc


Компонент TSQLStoredProc инкапсулирует функциональность хранимых процедур для их выполнения в рамках технологии dbExpress. Он подобен другим своим аналогам. Подробнее о функциях компонентов хранимых процедур см. часть III. Имя хранимой процедуры определяется свойством

property StoredProcName: string;

Для работы с входными и выходными параметрами предназначено свойство

property Params: TParams;

Внимание

При работе с параметрами желательно использовать обращение к конкретному параметру по имени при помощи метода ParamByName. При работе с некоторыми серверами порядок следования параметров до выполнения процедуры и после может изменяться.

Процедура выполняется методом

function ExecProc: Integer; virtual;

если она не возвращает набор данных. Иначе используются свойство Active или метод open.

Если хранимая процедура возвращает несколько связанных наборов данных (подобно иерархическим запросам ADO), доступ к следующему набору данных осуществляет метод

function NextRecordSet: TCustomSQLDataSet;

автоматически создавая объект типа TCustomSQLDataSet для инкапсуляции новых данных. Возврат к предыдущему набору данных возможен, если вы определили объектные переменные для каждого набора данных:

var SecondSet: TCustomSQLDataSet;

MyProc.Open;

while Not MyProc.Eof do

begin

{...}

Next; end;

SecondSet := MyProc.NextRecordSet; 

SecondSet.Open; {...}

SecondSet.Close; 

MyProc.Close;

 

Компонент TSimpleDataSet


Компонент TSimpleDataSet обеспечивает кэширование полученных данных и сделанных изменений на стороне клиента и последующую передачу их на сервер для фиксации. В отличие от компонента TClientDataSet, основным назначением которого является обслуживание набора данных, полученного от удаленного сервера при помощи серверных компонентов DataSnap, компонент TSimpleDataSet призван быть лишь средством редактирования набора данных в технологии dbExpress.

Компонент использует двунаправленный курсор и позволяет редактировать данные, правда только в режиме кэширования (см. гл. 22).

Таким образом, компонент TSimpleDataSet позволяет исправить основные недостатки технологии dbExpress.

Для подключения к источнику данных компонент использует свойство

property DBConnection: TSQLConnection;

которое позволяет связать его с соединением TSQLConnection (см. выше). или свойство

property ConnectionName: string;

которое позволяет выбрать тип соединения dbExpress напрямую.

При этом у компонента отсутствует механизм создания удаленного доступа к данным, представленный у компонента TclientDataSet свойствами RemoteServer И ProviderName.

После создания соединения с сервером БД можно определить тип используемой команды, подобно компоненту TSQLDataSet.

Тип команды определяется свойством

TSQLCommandType = (ctQuery, ctTable, ctStoredProc); 

property CommandType: TSQLCommandType;

А содержание команды задает свойство

property CoinmandText: string;

После этого компонент можно связывать с компонентами отображения данных, просматривать и редактировать данные.

Для передачи на сервер сделанных и сохраненных в локальном кэше изменений используется метод

function ApplyUpdates(MaxErrors: Integer); Integer; virtual;

где параметр MaxErrors определяет максимально возможное число ошибок при сохранении. Обычно этому параметру присваивается -1, что снимает ограничение на число ошибок. Метод

function Reconcile(const Results: OleVariant): Boolean;

очищает локальный кэш компонента от записей, которые успешно сохранены на сервере.

Отменить локальные изменения можно методом

procedure CancelUpdates;

Обратите внимание, что в компоненте действуют традиционные методы набора данных Edit, Post, Cancel, Apply, Insert, Delete. Но они оказывают влияние только на записи, кэшированные локально. Вы можете сколько угодно редактировать набор данных при помощи перечисленных методов, но они будут изменять только содержимое кэша. Настоящее сохранение на сервере осуществляется методом Appiyupdates.

Данные между сервером и компонентом пересылаются пакетами. Доступ к текущему пакету возможен при помощи свойства

property Data: OleVariant;

Сделанные изменения содержатся в свойстве

property Delta: OleVariant;

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

property PacketRecords: Integer;

которое задает число записей в пакете. Автоматическое назначение пакетов включается

PacketRecords := -1

Если значение PacketRecords равно 0, между клиентом и сервером пересылаются только метаданные.

Если свойство PacketRecords больше нуля, то необходимо вручную организовывать подкачку данных с сервера. Для этого используется метод

function GetNextPacket: Integer;

Для организации такой подкачки вполне подойдут методы-обработчики событий

property BeforeGetRecords: TRemoteEvent;

 property AfterGetRecords: TRemoteEvent;

В компоненте TSimpleDataSet развиты средства работы с одиночными записями. Можно просмотреть общее число записей

property RecordCount: Integer;

и номер текущей записи

property RecNo: Integer;

Размер одной записи сохраняется в свойстве

property RecordSize: Word;

Все изменения, сделанные в текущей записи, отменяются методом

procedure RevertRecord;

Обновить значение полей для текущей записи с сервера можно методом

procedure RefreshRecord;

Обработка исключительных ситуаций для компонента TSimpleDataSet состоит из двух этапов.

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

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

TReconcileErrorEvent = procedure(DataSet: TCustomClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction) 

of object; property OnReconcileError: TReconcileErrorEvent;

который срабатывает, если с сервера пересылается сообщение об ошибке. Информация об ошибке находится в параметре E: EReconcileError.

Более детальная информация о клиентских наборах данных содержится в гл. 22.

 

Способы редактирования данных


Несмотря на декларированные недостатки технологии dbExpress — однонаправленные курсоры и невозможность редактирования — существуют программные способы уменьшить масштаб проблемы или даже решить ее.

Во-первых, в нашем распоряжении имеется компонент TSimpleDataSet, который реализует двунаправленный курсор и обеспечивает редактирование данных путем их кэширования на клиентской стороне.

Во-вторых, редактирование можно обеспечить настройкой и выполнением запросов SQL INSERT, UPDATE и DELETE.

У каждого способа есть свои преимущества и недостатки.

Компонент TSimpleDataSet безусловно хорош. Он технологичен, относительно прост в использовании и, главное, прячет всю функциональность за несколькими свойствами и методами. Но локальное кэширование изменений подходит далеко не для всех приложений.

Например, при многопользовательском интенсивном доступе к данным с их редактированием при локальном кэшировании могут возникнуть проблемы с целостностью и адекватностью данных. Самый распространенный пример: продавец в строительном супермаркете, обслуживая покупателя в пик летних ремонтов, резервирует несколько наименований ходовых товаров. Но покупатель замешкался, выбирая обои и плитку. А за это время другой продавец уже продал другому покупателю (заказ первого покупателя еще находится в локальном кэше!) часть его товаров.

Конечно, фиксацию изменений на сервере можно выполнять после локального сохранения каждой записи, но это приведет к загрузке соединения и снижению эффективности системы.

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

Рассмотрим небольшой пример реализации обоих способов. Приложение Demo DBX использует соединение с сервером InterBase. Подключена тестовая база данных \Borland Shared\Data\MastSQL.gdb.

 Листинг 17.1. Пример приложения dbExpress с редактируемыми наборами данных

implementation

 {$R *.dfm}

procedure TfmDemoDBX.FormCreate(Sender: TObj ect);

 begin

tblVens.Open;

cdsGusts.Open;

 end;

procedure TfmDemoDBX.FormDestroy(Sender: TObject);

 begin

tblVens.Close;

cdsCusts.Close;

 end;

{Editing feature with updating query}

procedure TfmDemoDBX.tblVensAfterScroll(DataSet: TDataSet);

 begin

edVenNo.Text := tblVens.FieldByName('VENDORNO').AsString; 

edVenName.Text := tblVens.FieldByName('VENDORNAME').AsString; edVenAdr.Text := tblVens.FieldByName('ADDRESS1')AsString;

 edVenCity.Text := tblVens.FieldByName('CITY').AsString; 

edVenPhone.Text := tblVens.FieldByName('PHONE').AsString; 

end;

procedure TfmDemoDBX.sbCancelClick(Sender: TObject);

 begin

tblVens.First; 

end;

procedure TfmDemoDBX.sbNextClick(Sender: TObject);

 begin

tblVens.Next; end;

procedure TfmDemoDBX.sbPostClick(Sender: TObject);

 begin

with quUpdate do

try

ParamByName(4dx').Aslnteger :=

 tblVens.FieldByName('VENDORNO').Aslnteger; 

ParamByName('No').AsString := edVenNo.Text; 

ParamByName('Name').AsString := edVenName.Text;

 ParamByName('Adr').AsString := edVenAdr.Text;

ParamByName('City').AsString := edVenCity.Text;

 ParamByName('Phone1).AsString := edVenPhone.Text; 

ExecSQL; except

MessageDlg('Vendor''s info post error', mtError, [mbOK], 0);

 tblVens.First;

 end;

 end;

{Editing feature with cached updates}

procedure TfmDemoDBX.cdsCustsAfterPost(DataSet: TDataSet);

begin

cdsCusts.ApplyUpdates(-1);

 end;

procedure TfmDemoDBX.cdsCustsReconcileError(DataSet: 

TCustomClientDataSet;

E: EReconcileError; UpdateKind: TUpdateKind;

var Action: TReconcileAction);

begin

MessageDlg('Customer''s info post error', mtError, [mbOK], 0);

cdsCusts.CancelUpdates;

 end;

end.

Для просмотра и редактирования выбраны таблицы Vendors и Customers. Первая таблица подключена через настроенное соединение (компонент cnMast) к компоненту tbivens типа TSQLTable. Значение пяти полей отображается в обычных компонентах TEdit, т. к. компоненты отображения данных, связанные с компонентом dbExpress через компонент TDataSource, работают только в режиме просмотра, не позволяя редактировать данные (рис. 17.2).

Использование метода-обработчика AfterScroll позволило легко решить проблему заполнения компонентов TEdit при навигации по набору данных. Для сохранения сделанных изменений (нажатие на кнопку sbPost) используется компонент quupdate типа TSQLQuery. В параметрах запроса передаются текущие значения полей из компонентов TEdit. Так как в этом случае работает однонаправленный курсор, проблема обновления набора данных после выполнения модифицирующего запроса не возникает и набор данных обновляется только при вызове метода First компонента tbivens.

Вторая таблица подключена через тот же компонент cnMast к компоненту cdsCusts типа TSimpleDataSet. Он работает в табличном режиме. Данные отображаются в обычном компоненте TDBGrid.

Рис. 17.2. Окно приложения Demo dbExpress

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

Для компонента cdscusts также предусмотрена простейшая обработка исключительных ситуаций, возникающих на сервере. Обратите также внимание на настройку компонента cnMast типа TSQLConnection. Свойства KeepConnection И LoginPrompt со значениями False обеспечивают открытие наборов данных при создании формы и автоматическое закрытие соединения при закрытии приложения с минимальным исходным кодом.

 

Интерфейсы dbExpress


Технология dbExpress основана на использовании четырех базовых интерфейсов, методы которых применяются во всех компонентах dbExpress. При серьезной работе с технологией или при проектировании собственных компонентов информация об этих интерфейсах будет полезна.

 

Интерфейс ISQLDriver


Интерфейс ISQLDriver инкапсулирует всего три метода для обслуживания драйвера dbExpress. Экземпляр интерфейса создается для соединения и обеспечивает его связь с драйвером.

 Методы

function SetOption(eDOption: TSQLDriverOption; PropValue: Longlnt): 

SQLResult; stdcall;

function GetOption(eDOption: TSQLDriverOption; PropValue: Pointer;

 MaxLength: Smalllnt; out Length: Smalllnt): SQLResult; stdcall;

позволяют работать с параметрами драйвера. А метод

function getSQLConnection(out pConn: ISQLConnection): SQLResult; stdcall;

возвращает указатель на интерфейс связанного с драйвером соединения ISQLConnection.

Получить доступ к интерфейсу ISQLDriver разработчик может, использовав защищенное свойство

property Driver: ISQLDriver read FSQLDriver;

компонента TSQLConnection.

 

Интерфейс ISQLConnection


Интерфейс ISQLConnection обеспечивает работу соединения. Он передает запросы серверу и возвращает результаты, создавая экземпляры интерфейса iSQLCommand; управляет транзакциями; поддерживает передачу метаданных при помощи интерфейса ISQLMetaData.

Для открытия соединения используется метод

function connect(ServerName: PChar; UserName: PChar; Password: PChar): SQLResult; stdcall;

где ServerName — имя базы данных, UserName И Password — имя и пароль пользователя.

Закрывает соединение метод

function disconnect: SQLResult; stdcall;

Параметры соединения управляются методами

function SetOption(eConnectOption: TSQLConnectionOption; lvalue: Longlnt): SQLResult; stdcall;

function GetOption(eDOption: TSQLConnectionOption; PropValue: Pointer; MaxLength: Smalllnt; out Length: Smalllnt): SQLResult; stdcall;

Для обработки запроса, проходящего через соединение, создается интерфейс ISQLCommand 

function getSQLCommand(out pComm: ISQLCommand): SQLResult; stdcall;

 Обработка транзакций осуществляется тремя методами:

function beginTransaction(TranID: LongWord): SQLResult;

 stdcall; function commit(TranID: LongWord): SQLResult;

 stdcall; function rollback(TranID: LongWord): SQLResult; stdcall;

При помощи метода

function getErrorMessage(Error: PChar): SQLResult; overload; stdcall;

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

Например, можно написать собственную процедуру контроля ошибок примерно по такому образцу:

procedure CheckError(IConn: ISQLConnection);

 var FStatus: SQLResult;

FSize:SmallInt;

FMessage: pChar;

begin

FStatus := IConn.getErrorMessageLen(FSize); 

if (FStatus = SQL_SUCCESS)and(FSize > 0) then

 begin

FMessage := AllocMem(FSize + I);

 FStatus := IConn.getErrorMessage(FMessage);

 if FStatus = SQL_SUCCESS

then MessageDlg (FMessage, mtError, [rnbOK] , 0)

 else

 MessageDlg('Checking error', mtWarning, [mbOK], 0) ;

 if Assigned(FMessage)

then FreeMem(FMessage);

 end;

 end;

Доступ к интерфейсу isQLConnection можно получить через свойство

 property SQLConnection: ISQLConnection; 

компонента TSQLConnection.

 

Интерфейс ISQLCommand


Интерфейс isQLCommand обеспечивает функционирование запроса dbExpress. Компоненты dbExpress, работающие с наборами данных, используют его для реализации своих методов.

Параметры запроса устанавливаются методом

function setParameter(ulParameter: Word; ulChildPos: Word; eParamType: TSTMTParamType; uLogType: Word; uSubType: Word; iPrecision: Integer; iScale: Integer; Length: LongWord; pBuffer: Pointer; llnd: Integer): SQLResult; stdcall;

где ulParameter — порядковый номер параметра; если параметр является дочерним для сложных типов данных, ulchildPos задает его порядковый номер; eParamType задает тип параметра (входной, выходной, смешанный); uLogType — тип данных параметра; uSubType — вспомогательный параметр типа данных; iscale — максимальный размер значения в байтах; iPrecision — максимальная точность типа данных; Length — размер буфера; pBuffer — буфер, содержащий значение параметра; lInd — флаг, определяющий, может ли параметр иметь нулевое значение.

Для каждого параметра метод вызывается снова. Информацию о параметре можно получить, используя метод

function getParameter(ParameterNumber: Word; ulChildPos: Word; Value: 

Pointer; Length: Integer; var IsBlank: Integer): SQLResult; stdcall;

где ParameterNumber — порядковый номер параметра; если параметр является дочерним для сложных типов данных, ulchildPos задает его порядковый номер; value — указатель на буфер значения параметра; Length — размер буфера; isBlank — признак незаполненного параметра.

Метод

function prepare(SQL: PChar; ParamCount: Word): SQLResult; stdcall;

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

function execute(var Cursor: ISQLCursor): SQLResult; stdcall;

который возвращает в параметре интерфейс курсора, если запрос выполнен. Или метод

function executelmmediate(SQL: PChar; var Cursor: ISQLCursor): SQLResult; stdcall;

который выполняет запрос, не требующий подготовки (не имеющий параметров). Он также возвращает в параметре cursor готовый интерфейс курсора, если запрос выполнен успешно. Текст запроса определяется параметром SQL.

И метод

function getNextCursor(var Cursor: ISQLCursor): SQLResult; stdcall;

определяет в параметре Cursor курсор следующего набора данных, если выполнялась хранимая процедура, которая возвращает несколько наборов данных.

Интерфейс iSQLCommand используется компонентом TCustomSQLDataSet и недоступен потомкам.

 

Интерфейс ISQLCursor


Интерфейс ISQLCursor обладает совокупностью методов, которые помогут получить информацию о полях курсора, а также значения этих полей. Все эти методы имеют одинаковое представление. Для получения нужной информации необходимо задать порядковый номер поля в структуре курсора.

Метод

function next: SQLResult; stdcall;

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

Интерфейс ISQLCursor используется компонентом TCustomSQLDataSet и недоступен потомкам.

 

Отладка приложений с технологией dbExpress


Наряду с обычными методами отладки исходного кода, в dbExpress существует возможность контроля запросов, проходящих на сервер через соединение. Для этого используется компонент TSQLMonitor.

Через свойство

property SQLConnection: TSQLConnection;

компонент связывается с отлаживаемым соединением. Затем компонент включается установкой Active = True.

Теперь во время выполнения приложения сразу после открытия соединения свойство

property TraceList: TStrings;

будет заполняться информацией обо всех проходящих командах. Содержимое этого списка можно сохранить в файле при помощи метода

procedure SaveToFile(AFileName: string);

Эту же информацию можно автоматически добавлять в текстовый файл, определяемый свойством

property FileName: string;

но только тогда, когда свойство

property AutoSave: Boolean;

будет иметь значение True. Свойство

property MaxTraceCount: Integer;

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

property TraceCount: Integer;

Перед записью команды в список вызывается метод-обработчик

TTraceEvent = procedure(Sender: TObject; CBInfo: pSQLTRACEDesc;

 var LogTrace: Boolean) of object; 

property OnTrace: TTraceEvent;

а сразу после записи в список вызывается

TTraceLogEvent = procedure (Sender: TObject; CBInfo: pSQLTRACEDesc) of object;

property OnLogTrace: TTraceLogEvent;

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

Если же компонент TSQLMonitor не подходит, можно воспользоваться методом

procedure SetTraceCallbackEvent(Event: TSQLCallbackEvent; IClientlnfo: Integer);

компонента TSQLConnection. Параметр процедурного типа Event определяет функцию, которая будет вызываться при выполнении каждой команды. Параметр iclientinfo должен содержать любое число.

Он позволяет разработчику самостоятельно определить функцию типа

TSQLCallbackEvent:

TRACECat = TypedEnum;

TSQLCallbackEvent = function(CallType: TRACECat; CBInfo: Pointer): CBRType; stdcall;

Эта функция будет вызываться каждый раз при прохождении команды. Текст команды будет передаваться в буфер CBInfo. Разработчику необходимо лишь выполнить запланированные действия с буфером внутри функции.

Рассмотрим в качестве примера следующий исходный код.

function GetTracelnfо(CallType: TRACECat; CBInfo: Pointer): CBRType;

stdcall;

begin

if Assigned(Forml.TraceList) then Forml.TraceList.Add(pChar(CBinfo));

 end;

procedure TForml.MyConnectionBeforeConnect(Sender: TObject);

 begin

TraceList := TStringList.Create; 

end;

procedure TForml.MyConnectionAfterDisconnect(Sender: TObject);

begin

if Assigned(TraceList) then

begin TraceList.SaveToFile('с:\Temp\TraceInfo.txt');

TraceList.Free;

 end;

 end;

procedure TForml.StartBtnClick(Sender: TObject);

 begin

MyConnection.SetTraceCallbackEvent(GetTracelnfo, 8);

MyConnection.Open;

{...}

MyConnection.Close;

 end;

Перед открытием соединения в методе-обработчике BeforeConnection создается объект типа TStringList. После закрытия соединения этот объект сохраняется в файле и уничтожается.

Перед открытием соединения (метод-обработчик нажатия кнопки Start) при помощи метода SetTraceCallbackEvent с соединением связывается функция GetTracelnfo.

Таким образом, по мере прохождения команд информация о них будет накапливаться в списке. После закрытия соединения список сохраняется в текстовом файле.

Примечание 

В своей работе компонент TSQLMonitor также использует вызовы метода SetTraceCallbackEvent. Поэтому одновременно применять компонент и собственные функции нельзя.

 

Распространение приложений с технологией dbExpress


Готовое приложение, использующее технологию dbExpress, можно поставлять заказчикам двумя способами.

Вместе с приложением поставляется динамическая библиотека для выбранного сервера (см. колонку "Драйвер" табл. 17.1). Она находится в папке \Delphi7\Bin.

Дополнительно, если в приложении используется компонент TSimpleDataSet, необходимо включить в поставку динамическую библиотеку Midas.dll.

Приложение компилируется вместе со следующими DCU-файлами: dbExpInt.dcu, dbExpOra.dcu, dbExpDb2.dcu, dbExpMy.dcu (в зависимости от выбранного сервера). Если в приложении используется компонент TSimpieDataSet, следует добавить файлы Crtl.dcu и MidasLib.dcu. В результате необходимо поставлять только исполняемый файл приложения.

Если дополнительная настройка соединений не требуется, файл dbxconnections.ini не нужен.

 

Технология dbExpress предназначена для создания




Технология dbExpress предназначена для создания приложений, требующих быстрого доступа к базам данных, хранящимся на серверах SQL. Доступ осуществляется при помощи небольших драйверов, реализованных в виде динамических библиотек. В настоящее время созданы драйверы для четырех серверов баз данных. Это:
 DB2;  InterBase;   MySQL;  Oracle. Технология dbExpress реализована на основе использования стандартных типов компонентов доступа к данным, проста при распространении (исполняемый файл приложения или одна-две динамические библиотеки). Поддерживает кроссплатформенную разработку для Linux и легко интегрируется в приложения CLX.
К недостаткам технологии нужно отнести использование однонаправленных курсоров и ограниченные возможности по редактированию (редактирование возможно только при кэшировании изменений на клиенте или выполнением специальных модифицирующих запросов).