Технология dbExpress (статья)

=== Технология 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 обеспечивает доступ к серверу баз данных при помощи драйвера, реализованного как динамическая библиотека. Для каждого сервера имеется своя динамическая библиотека.
 Драйверы dbExpress
Сервер БД 	Драйвер 	Клиентское ПО
DB2 	Dbexpdb2.dll	Db2cli.dll
InterBase 	Dbexpint.dll	GDS32.DLL
Informix 	Dbexpinf.dll	Isqlb09a.dll
MS SQL Server	Dbexpmss.dll	OLE DB
MySQL 	Dbexpmys.dll	LIBMYSQL.DLL
Oracle 	Dbexpora.dll	OCI.DLL
Перечисленные в табл. файлы находятся в папке \Delphi7\Bin.
Для доступа к данным сервера драйвер должен быть установлен на компьютере клиента. Для доступа к данным драйвер взаимодействует с клиентским ПО сервера, которое также должно быть инсталлировано на клиентской стороне.
Стандартные настройки для каждого драйвера хранятся в файле
 \Borland Shared\DBExpress\dbxdrivers.ini.
=== Соединение с сервером баз данных ===
Для создания соединения с сервером в рамках технологии dbExpress приложение должно использовать компонент TSQLConnection. Это обязательный компонент, все остальные компоненты связаны с ним и используют его для получения данных.
После переноса этого компонента в модуль данных или на форму необходимо выбрать тип сервера и настроить параметры соединения.
Свойство ConnectionName позволяет выбрать из выпадающего списка конкретное настроенное соединение. По умолчанию разработчику доступно по одному настроенному соединению для каждого сервера БД. После выбора соединения автоматически устанавливаются значения свойств:
// определяет используемый драйвер; 
property DriverName: string;
// задает динамическую библиотеку драйвера dbExpress; 
property LibraryName: string;
// определяет динамическую библиотеку клиентского ПО сервера 
property VendorLib: string;
// список этого свойства содержит настройки для выбранного соединения. 
property Params: TStrings;
При необходимости все перечисленные свойства можно установить дополнительно.
Разработчик может дополнять и изменять список настроенных соединений. Для этого используется специализированный Редактор соединений dbExpress Connections (рис. 17.1). Он открывается после двойного щелчка на компоненте TSQLConnection или выбора команды Edit Connection Properties из всплывающего меню компонента.
В списке слева располагаются существующие соединения. В правой части для выбранного соединения отображаются текущие настройки. При помощи кнопок на Панели инструментов можно создавать, переименовывать и удалять соединения. Настройки также можно редактировать.
Список соединений в левой части соответствует списку выбора свойства ConnectionName в Инспекторе объектов. Настройки соединения из правой части отображаются в свойстве Params.
Настроенные соединения и их параметры сохраняются в файле
 \Borland Shared\DBExpress\dbxconnections.ini.
; Примечание: При установке Delphi 7 поверх Delphi 6 сведения о соединениях из старого файла dbxconnections.ini добавляются в новый.
Конкретные значения настроек соединений зависят от используемого сервера БД и требований приложения (табл. 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 готов к работе.
Свойство Connected открывает соединение с сервером при значении True. Аналогичную операцию выполняет метод Open()
После открытия соединения все компоненты dbExpress, инкапсулирующие наборы данных и связанные с открытым компонентом TSQLConnection, получают доступ к базе данных.
Соединение закрывается тем же свойством Connected или методом 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;
Свойство LoginPrompt определяет, нужно ли отображать диалог авторизации пользователя перед открытием соединения.
О текущем состоянии соединения можно судить по значению свойства
TConnectionState = (csStateClosed, csStateOpen, csStateConnecting, csStateExecuting, csStateFetching, csStateDisconnecting);
property ConnectionState: TConnectionState;
Параметры соединения можно настраивать на этапе разработки в Инспекторе объектов или Редакторе соединений (см. рис. 17.1). Также это можно сделать и непосредственно перед открытием соединения, используя свойство Params или метод LoadParamsFromlniFile() который загружает заранее подготовленные параметры из INI-файла. Проверить успешность этой операции можно при помощи свойства ParamsLoaded значение 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 позволяет выполнять некоторые операции с подключенными наборами данных и следить за их состоянием.
Свойство DataSetCount возвращает число подключенных через данное соединение наборов данных.
Но это только активные наборы данных, переданные в связанные компоненты. Общее число выполняющихся в настоящий момент запросов возвращает свойство ActiveStatements
Если сервер БД установил для данного соединения максимальное число одновременно выполняющихся запросов, то оно доступно в свойстве MaxStmtsPerConn
Поэтому перед открытием набора данных можно выполнять следующий код, который повысит надежность приложения:
if MyQuery.SQLConnection.ActiveStatements <= MyQuery.SQLConnection.MaxStmtsPerConn then
 MyQuery.Open
else
 MessageDlg ('Database connection is busy', mtWarning, [mbOK] , 0);
В случае возникновения непредвиденной ситуации все открытые через данное соединение наборы данных можно быстро закрыть методом 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;
Если запрос не имеет настраиваемых параметров и не возвращает набор данных, можно использовать функцию ExecuteDirect(), которая возвращает 0 в случае успешного выполнения запроса или код ошибки.
Метод GetTableNames() возвращает список таблиц базы данных. Параметр SystemTables позволяет включать в формируемый список List системные таблицы.
Метод GetTableNames() дополнительно управляется свойством
TTableScope = (tsSynonym, tsSysTable, tsTable, tsView);
TTableScopes = set of TTableScope;
property TableScope: TTableScopes;
которое позволяет задать тип таблиц, имена которых попадают в список. Для каждой таблицы можно получить список полей, использовав метод GetFieldNames() и список индексов при помощи метода GetIndexNames()
В обоих методах список возвращаемых значений содержится в параметре List.
Аналогичным образом метод GetProcedureNames() возвращает список доступных хранимых процедур, а метод GetProcedureParams() определяет параметры отдельной процедуры.
=== Транзакции ===
Подобно своим аналогам в 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.
Некоторые серверы БД не поддерживают транзакции, и для определения этого факта используется свойство TransactionsSupported
Если соединение уже находится в транзакции, свойству InTransaction присваивается значение True. Поэтому, если сервер не поддерживает множественные транзакции, всегда полезно убедиться, что соединение не обслуживает начатую транзакцию:
var
 Translnfo: TTransactionDesc;
begin
 if Not MyConnection.InTransaction then
 try
 MyConnection.StartTransaction(Translnfo); {...}
 MyConnection.Commit(Translnfo);
 except
 MyConnection.Rollback(Translnfo);
 end;
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 CommandText: string;
содержащее текст команды.
Если серверу передается запрос SQL (CommandType = ctQuery), свойство CommandText содержит текст запроса. Если это команда на получение таблицы, свойство CommandText содержит имя таблицы, а далее с использованием имени таблицы создается запрос SQL на получение всех полей этой таблицы. Если необходимо выполнить процедуру, свойство CommandText содержит имя этой процедуры.
Текст команды, которая реально передается на сервер для выполнения, содержится в защищенном свойстве NativeCommand
Для использования в табличном представлении существует свойство SortFieldNames определяющее порядок сортировки записей табличного набора данных. Свойство должно содержать список полей, разделенных точкой с запятой. Это свойство используется для создания выражения ORDER BY для генерируемой команды.
Для обработки исключительных ситуаций в классах - потомках может быть использовано защищенное свойство LastError, которое возвращает текст последней ошибки dbExpress.
Для ускорения работы набора данных можно отключить получение от сервера метаданных об объекте запроса (таблицы, процедуры, полей, индексов), которые обычно направляются клиенту вместе с результатом запроса. Для этого свойству NoMetadata присваивается значение True.
Однако пользоваться им нужно осторожно, т. к. для некоторых видов команд метаданные необходимы (это операции с использованием индексов).
Разработчик может управлять процессом получения метаданных. Для этого необходимо заполнить структуру
TSchemaType = (stNoSchema, stTables, stSysTables, stProcedures, stColumns, stProcedureParams, stIndexes);
TSchemalnfo = record
 EType: TSchemaType;
 ObjectName: String;
 Pattern: String;
end;
которая доступна через защищенное свойство property Schemalnfo а значит, может использоваться только при создании новых компонентов на основе TCustomSQLDataSet.
Параметр FTуре определяет тип требуемой информации. Параметр ObjectName - имя таблицы или хранимой процедуры, если в параметре FType указаны поля, индексы или параметры процедур.
; Внимание: Если компонент должен получать результирующий набор данных, параметр FType должен обязательно иметь значение stNoSchema. При изменении значения свойства CommandText это условие выполняется автоматически.
Параметр Pattern определяет, какие ограничения накладываются на метаданные. Он содержит символьную маску, подобную свойству Mask многих визуальных компонентов. Последовательность символов маски обозначается символом %, единичный символ определяется символом .
При необходимости использовать управляющие символы в качестве маскирующих, применяются двойные символы %% и _.
Подобно свойству Tag класса TComponent, класс TCustomSQLDataSet имеет строковое свойство DesignerData в котором разработчик может хранить любую служебную информацию. По существу, это просто лишняя строковая переменная, которую нет необходимости объявлять.
==== TSQLDataSet ====
Компонент TSQLDataSet является универсальным и позволяет выполнять запросы SQL (подобно TSQLQuery), просматривать таблицы целиком (подобно TSQLTable)или выполнять хранимые процедуры (подобно TSQLStoredProc).
Для определения режима работы компонента используется свойство CommandType (см. выше).
Если ему присвоить значение 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).
Информация об используемых в результирующем наборе данных индексах сохраняется в свойстве IndexDefs
==== TSQLTable ====
Компонент TSQLTable предназначен для просмотра таблиц целиком и по основным функциям подобен своим аналогам TTаblе, TADOтаblе,TIBTаblе (подробнее о функциях компонентов таблиц см. часть III).
Для получения табличного набора данных компонент TSQLTable самостоятельно формирует запрос на сервер, используя для этого возможности, унаследованные от предка TCustomSQLDataSet.
Метод PrepareStatement() генерирует для выбранной таблицы текст запроса, который формируется компонентом для передачи на сервер.
Для определения имени таблицы используется свойство TableName, и, если компонент подключен к соединению, имя таблицы можно выбрать из списка.
Для подключения простых или составных индексов используются свойства IndexFieldNames, IndexFields, IndexName. А метод GetlndexNames() возвращает в параметр List список используемых индексов.
Связь между двумя наборами данных главный/подчиненный организуется свойствами MasterFields, MasterSource.
Компонент TSQLTable предоставляет разработчику некоторое подобие функций редактирования. Для удаления всех записей из связанной с компонентом таблицы на сервере используется метод DeleteRecords()
==== TSQLQuery ====
Компонент TSQLQuery повторяет функциональность своих аналогов в BDE, ADO, InterBase Express и позволяет выполнять на сервере запросы SQL клиента. Подробнее о функциях компонентов запросов SQL см. часть III.
Текст запроса содержится в свойстве SQL а его простое строковое представление в свойстве Text
Если запрос возвращает набор данных, его выполнение осуществляется свойством Active или методом open. В противном случае используется метод
function ExecSQL(ExecDirect: Boolean = False): Integer; override;
Параметр ExecDirect = False означает, что запрос не имеет настраиваемых параметров.
==== TSQLStoredProc ====
Компонент TSQLStoredProc инкапсулирует функциональность хранимых процедур для их выполнения в рамках технологии dbExpress. Он подобен другим своим аналогам. Подробнее о функциях компонентов хранимых процедур см. часть III. Имя хранимой процедуры определяется свойством StoredProcName
Для работы с входными и выходными параметрами предназначено свойство Params
; Внимание: При работе с параметрами желательно использовать обращение к конкретному параметру по имени при помощи метода ParamByName. При работе с некоторыми серверами порядок следования параметров до выполнения процедуры и после может изменяться.
Процедура выполняется методом ExecProc() если она не возвращает набор данных. Иначе используются свойство Active или метод Open().
Если хранимая процедура возвращает несколько связанных наборов данных (подобно иерархическим запросам ADO), доступ к следующему набору данных осуществляет метод NextRecordSet() автоматически создавая объект типа TCustomSQLDataSet для инкапсуляции новых данных. Возврат к предыдущему набору данных возможен, если вы определили объектные переменные для каждого набора данных:
var
 SecondSet: TCustomSQLDataSet;
begin
 MyProc.Open;
 while Not MyProc.Eof do
 begin
 {...}
 Next;
 end;
 SecondSet := MyProc.NextRecordSet;
 SecondSet.Open; {...}
 SecondSet.Close;
 MyProc.Close;
end;
==== TSimpleDataSet ====
Компонент TSimpleDataSet обеспечивает кэширование полученных данных и сделанных изменений на стороне клиента и последующую передачу их на сервер для фиксации. В отличие от компонента TClientDataSet, основным назначением которого является обслуживание набора данных, полученного от удаленного сервера при помощи серверных компонентов DataSnap, компонент TSimpleDataSet призван быть лишь средством редактирования набора данных в технологии dbExpress.
Компонент использует двунаправленный курсор и позволяет редактировать данные, правда только в режиме кэширования (см. гл. 22).
Таким образом, компонент TSimpleDataSet позволяет исправить основные недостатки технологии dbExpress.
Для подключения к источнику данных компонент использует свойство DBConnection которое позволяет связать его с соединением TSQLConnection (см. выше). или свойство ConnectionName которое позволяет выбрать тип соединения dbExpress напрямую.
При этом у компонента отсутствует механизм создания удаленного доступа к данным, представленный у компонента TClientDataSet свойствами RemoteServer И ProviderName.
После создания соединения с сервером БД можно определить тип используемой команды, подобно компоненту TSQLDataSet.
Тип команды определяется свойством
TSQLCommandType = (ctQuery, ctTable, ctStoredProc);
property CommandType: TSQLCommandType;
А содержание команды задает свойство CommandText
После этого компонент можно связывать с компонентами отображения данных, просматривать и редактировать данные.
Для передачи на сервер сделанных и сохраненных в локальном кэше изменений используется метод ApplyUpdates() где параметр MaxErrors определяет максимально возможное число ошибок при сохранении. Обычно этому параметру присваивается -1, что снимает ограничение на число ошибок. Метод Reconcile() очищает локальный кэш компонента от записей, которые успешно сохранены на сервере.
Отменить локальные изменения можно методом CancelUpdates()
Обратите внимание, что в компоненте действуют традиционные методы набора данных Edit, Post, Cancel, Apply, Insert, Delete. Но они оказывают влияние только на записи, кэшированные локально. Вы можете сколько угодно редактировать набор данных при помощи перечисленных методов, но они будут изменять только содержимое кэша. Настоящее сохранение на сервере осуществляется методом Appiyupdates.
Данные между сервером и компонентом пересылаются пакетами. Доступ к текущему пакету возможен при помощи свойства Data
Сделанные изменения содержатся в свойстве Delta
При этом разработчик может регулировать размер пакетов. Например, при ухудшении соединения можно уменьшить размер пакетов. Размер пакета определяется свойством PacketRecords, которое задает число записей в пакете. Автоматическое назначение пакетов включается PacketRecords := -1
Если значение PacketRecords равно 0, между клиентом и сервером пересылаются только метаданные.
Если свойство PacketRecords больше нуля, то необходимо вручную организовывать подкачку данных с сервера. Для этого используется метод GetNextPacket()
Для организации такой подкачки вполне подойдут методы-обработчики событий
property BeforeGetRecords: TRemoteEvent;
property AfterGetRecords: TRemoteEvent;
В компоненте TSimpleDataSet развиты средства работы с одиночными записями. Можно просмотреть общее число записей RecordCount и номер текущей записи RecNo
Размер одной записи сохраняется в свойстве RecordSize
Все изменения, сделанные в текущей записи, отменяются методом RevertRecord()
Обновить значение полей для текущей записи с сервера можно методом RefreshRecord()
Обработка исключительных ситуаций для компонента TSimpleDataSet состоит из двух этапов.
Во-первых, необходимо отслеживать ошибки на стороне клиента - это могут быть некорректный ввод данных, ошибки кэширования и т. д. В этом случае подходят все стандартные способы, применяемые для наборов данных.
Во-вторых, ошибки могут возникнуть при сохранении изменений на сервере. И поскольку само событие, приведшее к исключительной ситуации, возникает на другом компьютере или в другом процессе, для отслеживания таких ошибок используется специальный метод-обработчик
TReconcileErrorEvent = procedure(DataSet: TCustomClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind;
 var Action: TReconcileAction) of object;
property OnReconcileError: TReconcileErrorEvent;
который срабатывает, если с сервера пересылается сообщение об ошибке. Информация об ошибке находится в параметре E: EReconcileError.
=== Способы редактирования данных ===
Несмотря на декларированные недостатки технологии dbExpress - однонаправленные курсоры и невозможность редактирования - существуют программные способы уменьшить масштаб проблемы или даже решить ее.
Во-первых, в нашем распоряжении имеется компонент TSimpleDataSet, который реализует двунаправленный курсор и обеспечивает редактирование данных путем их кэширования на клиентской стороне.
Во-вторых, редактирование можно обеспечить настройкой и выполнением запросов SQL INSERT, UPDATE и DELETE.
У каждого способа есть свои преимущества и недостатки.
Компонент TSimpleDataSet безусловно хорош. Он технологичен, относительно прост в использовании и, главное, прячет всю функциональность за несколькими свойствами и методами. Но локальное кэширование изменений подходит далеко не для всех приложений.
Например, при многопользовательском интенсивном доступе к данным с их редактированием при локальном кэшировании могут возникнуть проблемы с целостностью и адекватностью данных. Самый распространенный пример: продавец в строительном супермаркете, обслуживая покупателя в пик летних ремонтов, резервирует несколько наименований ходовых товаров. Но покупатель замешкался, выбирая обои и плитку. А за это время другой продавец уже продал другому покупателю (заказ первого покупателя еще находится в локальном кэше!) часть его товаров.
Конечно, фиксацию изменений на сервере можно выполнять после локального сохранения каждой записи, но это приведет к загрузке соединения и снижению эффективности системы.
Использование модифицирующих запросов, с одной стороны, позволяет оперативно вносить изменения в данные на сервере, а с другой - требует больших затрат на программирование и отладку. Сложность кода в этом случае существенно выше.
Рассмотрим небольшой пример реализации обоих способов. Приложение Demo DBX использует соединение с сервером InterBase. Подключена тестовая база данных \Borland Shared\Data\MastSQL.gdb.
Пример приложения dbExpress с редактируемыми наборами данных
implementation
{$R *.dfm}
procedure TfmDemoDBX.FormCreate(Sender: TObject);
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('Vendors 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('Customers info post error', mtError, [mbOK], 0);
 cdsCusts.CancelUpdates;
end;
end.
Для просмотра и редактирования выбраны таблицы Vendors и Customers. Первая таблица подключена через настроенное соединение (компонент cnMast) к компоненту tbivens типа TSQLTable. Значение пяти полей отображается в обычных компонентах TEdit, т. к. компоненты отображения данных, связанные с компонентом dbExpress через компонент TDataSource, работают только в режиме просмотра, не позволяя редактировать данные
Использование метода-обработчика AfterScroll позволило легко решить проблему заполнения компонентов TEdit при навигации по набору данных. Для сохранения сделанных изменений (нажатие на кнопку sbPost) используется компонент quupdate типа TSQLQuery. В параметрах запроса передаются текущие значения полей из компонентов TEdit. Так как в этом случае работает однонаправленный курсор, проблема обновления набора данных после выполнения модифицирующего запроса не возникает и набор данных обновляется только при вызове метода First компонента tblVens.
Вторая таблица подключена через тот же компонент cnMast к компоненту cdsCusts типа TSimpleDataSet. Он работает в табличном режиме. Данные отображаются в обычном компоненте TDBGrid.
Для сохранения сделанных изменений здесь использован метод ApplyUpdates, размещенный в методе-обработчике 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 разработчик может, использовав защищенное свойство Driver компонента TSQLConnection.
==== ISQLConnection ====
Интерфейс ISQLConnection обеспечивает работу соединения. Он передает запросы серверу и возвращает результаты, создавая экземпляры интерфейса iSQLCommand; управляет транзакциями; поддерживает передачу метаданных при помощи интерфейса ISQLMetaData.
Для открытия соединения используется метод
function Connect(ServerName: PChar; UserName: PChar; Password: PChar): SQLResult; stdcall;
где ServerName - имя базы данных, UserName И Password - имя и пароль пользователя.
Закрывает соединение метод Disconnect()
Параметры соединения управляются методами
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 можно получить через свойство SQLConnection компонента 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; lInd: 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 ExecuteImmediate(SQL: PChar; var Cursor: ISQLCursor): SQLResult; stdcall;
который выполняет запрос, не требующий подготовки (не имеющий параметров). Он также возвращает в параметре Cursor готовый интерфейс курсора, если запрос выполнен успешно. Текст запроса определяется параметром SQL.
И метод
function getNextCursor(var Cursor: ISQLCursor): SQLResult; stdcall;
определяет в параметре Cursor курсор следующего набора данных, если выполнялась хранимая процедура, которая возвращает несколько наборов данных.
Интерфейс ISQLCommand используется компонентом TCustomSQLDataSet и недоступен потомкам.
=== ISQLCursor ===
Интерфейс ISQLCursor обладает совокупностью методов, которые помогут получить информацию о полях курсора, а также значения этих полей. Все эти методы имеют одинаковое представление. Для получения нужной информации необходимо задать порядковый номер поля в структуре курсора.
Метод Next() обновляет курсор, занося в него информацию из следующей строки набора данных.
Интерфейс ISQLCursor используется компонентом TCustomSQLDataSet и недоступен потомкам.
=== Отладка приложений с технологией dbExpress ===
Наряду с обычными методами отладки исходного кода, в dbExpress существует возможность контроля запросов, проходящих на сервер через соединение. Для этого используется компонент TSQLMonitor.
Через свойство SQLConnection компонент связывается с отлаживаемым соединением. Затем компонент включается установкой Active := True.
Теперь во время выполнения приложения сразу после открытия соединения свойство TraceList будет заполняться информацией обо всех проходящих командах. Содержимое этого списка можно сохранить в файле при помощи метода SaveToFile()
Эту же информацию можно автоматически добавлять в текстовый файл, определяемый свойством FileName, но только тогда, когда свойство AutoSave будет иметь значение True. Свойство MaxTraceCount определяет максимальное число контролируемых команд, а также управляет процессом контроля. При значении -1 ограничения снимаются, а при значении 0 контроль останавливается. Текущее число проверенных команд содержится в свойстве TraceCount
Перед записью команды в список вызывается метод-обработчик
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(Form1.TraceList) then Form1.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, можно поставлять заказчикам двумя способами.
Вместе с приложением поставляется динамическая библиотека для выбранного сервера. Она находится в папке \Delphi7\Bin.
Дополнительно, если в приложении используется компонент TSimpleDataSet, необходимо включить в поставку динамическую библиотеку Midas.dll.
Приложение компилируется вместе со следующими DCU-файлами: dbExpInt.dcu, dbExpOra.dcu, dbExpDb2.dcu, dbExpMy.dcu (в зависимости от выбранного сервера). Если в приложении используется компонент TSimpieDataSet, следует добавить файлы Crtl.dcu и MidasLib.dcu. В результате необходимо поставлять только исполняемый файл приложения.
Если дополнительная настройка соединений не требуется, файл dbxconnections.ini не нужен.
ID: 02685
Category:Базы данных
Category:dbExpress
Category:Статьи