Глава 22

Разработка клиентских приложений

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

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

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

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

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

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

Итак, в этой главе пойдет речь о возможностях среды разработки Delphi по созданию клиентских приложений для архитектуры клиент/сервер. Будут рассмотрены следующие вопросы.

Программная логика клиентских приложений

Клиентское приложение в первую очередь должно обеспечить связь с сервером и базой данных. Для решения этих задач необходимо использовать компоненты TSession и TDatabase. Их применение подробно рассматривается в главе 21. Все компоненты доступа к данным (ттаЬ1е, TQuery, TStoredProc) клиента должны обращаться к серверу через один или несколько компонентов TDatabase. Напомним, что это позволяет не только централизованно управлять соединением, но и проводить регистрацию пользователя на сервере и использовать транзакции-

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

В зависимости от конкретной задачи, клиентское приложение может быть мощным (так называемый "толстый" клиент) или слабым ("тонкий" клиент).

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

Для "тонкого" клиента суть всех ограничений сводится к требованию проводить обработку наборов данных на сервере. Клиентское ПО, по существу, лишь предоставляет пользователю удобный интерфейс для удаленного редактирования данных. Эта стратегия применяется для мощных серверов с относительно небольшим числом клиентов и позволяет существенно уменьшить затраты на клиентские рабочие места.

Итак, программная логика локальных приложений БД ни в коей мере не применима в архитектуре клиент/сервер. Например, следующий фрагмент вполне допустим в локальном приложении, но крайне нежелателен в клиентском приложении:

with DA.Parts do begin

Open;

While Not EOF dо

begin

Edit;

PartsOutputPrice.Value := PartsOutputPrice.Value*1.05;

Next; "''

end;

end;

В этом цикле отпускная цена каждого поступления увеличивается на 5%. При переходе к следующей записи при помощи процедуры Next происходит автоматическое подтверждение сделанных изменений. Если этот код будет работать в многоуровневом приложении, то сервер постоянно будет записывать изменения в таблицу Parts, которая будет надолго заблокирована этим клиентом.

Поэтому гораздо эффективнее будет работать модифицирующий запрос: UPDATE Parts SET OutputPrice °= OutputPrice*1.05

Еще лучше, если этот же запрос выполнит хранимая процедура.

Если приложение должно длительное время поддерживать набор данных в режиме редактирования, то для него необходимо включить режим кэширования изменений, что позволяет перенести в БД все накопленные изменения в одном запросе (например, при щелчке на кнопке ОК).

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

В клиентских приложениях желательно все операции выполнять при помощи запросов SQL, поэтому необходимо использовать компоненты TQuery и TUpdateSQL. Но это не значит, что компоненты TTable использовать запрещено. Чем же предпочтительнее компонент TQuery?

  1. Серверы БД работают с тем или иным диалектом языка SQL. При помощи запросов разработчик может получать такие выборки информации из целого ряда таблиц одновременно, которые принципиально невозможно получить компонентом TTable.
  2. При работе с сервером БД BDE в любом случае транслирует вызов клиента в запрос SQL. Поэтому лучше будет облегчить ему работу и написать запрос сразу.
  3. Разработчик может без особых хлопот существенно изменить текст запроса или его параметры (гл. 20). Это позволяет с минимальными затратами делать программный интерфейс одновременно гибким и мощным.
  4. Запрос может возвращать набор данных с произвольным числом полей, которое зависит только от самого запроса (в одном запросе можно объединить поля нескольких таблиц). Табличный компонент в лучшем случае может предоставить подмножество полей одной таблицы.
  5. Компонент TQuery открывается быстрее компонента TTable, так как второй при работе с динамическими полями полностью воссоздает всю информацию о всех полях таблицы. Компонент TQuery обновляет сведения только об указанных в запросе полях.

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

Хранимые процедуры

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

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

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

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

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

В целом хранимые процедуры имеют следующие преимущества.

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

Клиентское приложение только посылает сигнал на выполнение и значения параметров (если они есть).

Универсальность. Хранимые процедуры могут быть созданы для выполнения самых распространенных и самых сложных операций в базе данных. Любое клиентское приложение может использовать любую хранимую процедуру.

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

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

Создание хранимых процедур для сервера InterBase

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

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

Итак, для создания хранимой процедуры для сервера InterBase используется оператор create procedure ... as. Тело процедуры обозначается операторами begin и end. Внутри процедуры можно использовать любые синтаксически правильные конструкции языка SQL. Хранимые процедуры делятся на выполняемые процедуры и процедуры отбора.

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

CREATE PROCEDURE ClearArchParts AS BEGIN DELETE FROM ArchParts WHERE PartDat>'0l.01.96';

END

В хранимую процедуру любого типа через входные параметры можно передать нужные значения:

CREATE PROCEDURE ClearArchPartsl (MaxDate DATE) as

BEGIN DELETE FROM ArchParts WHERE PartDat>:MaxDate;

END

В теле процедуры место параметра обозначается двоеточием.

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

SELECT ... DO:

CREATE PROCEDURE FindOrder (OrdNo INTEGER) RETURNS(DocNo VARCHAR(10)) AS BEGIN FOR

SELECT DocNo

FROM ArchOrders

WHERE OrdNo = :OrdNo

INTO :DocNo DO

SUSPEND;

END

Возвращаемые процедурой значения перечисляются в операторе returns () через запятую. Оператор into связывает результаты выполнения запроса с передаваемыми из процедуры параметрами. В тексте запроса параметр обозначается двоеточием. Оператор suspend приостанавливает выполнение процедуры сразу после отправки очередного результата.

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

Доступ компонента к базе данных осуществляется через свойство DatabaseName. Хранимая процедура задается ее именем в свойстве storedProcName. При создании клиентского приложения в архитектуре клиент/сервер для соединения с сервером желательно использовать компонент TDatabase.

Если хранимая процедура не имеет входных параметров, то для ее выполнения используется метод Ехесргос. Например, компонент Storedproci можно связать с некоторой хранимой процедурой :

StoredProcl.DatabaseName := 'IBLOCAL';

StoredProcI.StoredProcName := 'GetSomeVaiue';

StoredProcI.ExecProc;

Если хранимая процедура имеет входные параметры, то для их определения используется свойство params типа Tparams (гл- 20). Для хранимой процедуры входные параметры задаются так:

StoredProcI.Params[0].AsDateTime := Now;

StoredProcI.Params[1].As Integer := SpinEditI.Value;

StoredProcI.Params[2].AsString := Editl.Text;

StoredProcI.ExecProc;

После задания входных параметров при помощи свойства params необходимо подготовить процедуру к выполнению. Для этого используется метод prepare. После выполнения процедуры необходимо освободить занятые ресурсы. Эту работу выполняет метод unprepare:

StoredProcI.Params[0].AsDateTime := Now;

StoredProcI.Params[1].Aslnteger := SpinEditI.Value;

StoredProcI.Params[2].AsString := Editl.Text;

StoredProcI.Prepare;

StoredProcI.ExecProc;

StoredProcI.UnPrepare ;

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

Для работы с параметрами также можно использовать метод paramByName, который возвращает нужный параметр по его имени:

StoredProcI.ParamByName('MaxDate').AsDateTime := Now;

StoredProcI.Prepare;

StoredProcI.ExecProc;

Если хранимая процедура возвращает единичный результат, то его можно получить в выходном параметре (или нескольких выходных параметрах). Для этого можно опять воспользоваться методом ParamByName или свойством Params:

with StoredProcl do begin Params[0].AsString := Edit1.Text;

Prepare ;

ExecProc;

UnPrepare;

Labell.Caption := Params[3].AsString;

end;

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

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

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

Для создания хранимой процедуры используется запрос с оператором

CREATE PROCEDURE.

CREATE PROCEDURE SumOrders (OrdDat DATE) RETURNS (DocNo VARCHAR(IO), OrdSum INTEGER) AS BEGIN FOR

SELECT DocNo, SUM(ItemCount*Price)

FROM ArchOrders

WHERE OrdDat > :OrdDat

GROUP BY DOCNO

INTO :DocNo, :OrdSum DO

SUSPEND;

END

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

SELECT * FROM SumOrders ('01.09.99')

В результате в наборе данных запроса будут содержаться суммы всех заказов после 1 сентября 1999 г.

Компонент TStoredProc

Компонент TStoredProc обеспечивает использование в приложениях хранимых процедур. Так как этот инструмент обработки данных используется серверами БД, то компоненты TStoredProc используются в многоуровневых приложениях. Прямым предком компонента является класс TDBDataSet. Поэтому результатом выполнения хранимой процедуры, может быть не только одиночный результат, но и полноценный набор данных. Средствами классов-предков выполняется и подключение компонента к базе данных.

Свойство DatabaseName определяет базу данных. Свойство StoredProcName задает имя хранимой процедуры (табл. 22.1).

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

Индексированный список всех параметров хранимой процедуры (входных и выходных) содержится в свойстве params. Состав параметров определяется текстом хранимой процедуры. Управление параметрами в списке осуществляется при помощи специализированного редактора, который открывается при щелчке на кнопке свойства в Инспекторе объектов (см. описание компонента TQuery в главе 20).

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

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

Перед выполнением хранимую процедуру необходимо подготовить. В частности, на этом этапе осуществляется передача параметров и выделение ресурсов. Эта операция выполняется автоматически при использовании методов Ехесргос и open или задается явно методом prepare.

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

Таблица 22.1. Свойства и методы компонента TStoredProc

Объявление

Тип

Описание

Свойства

property Overload: Word;

Рb

Идентификатор процедуры. Используется только для сервера Oracle

type TParamBindMode = (pbByName, pbByNumber) ;

property ParaiTiBi ndMode: TParamBindMode ;

Pb

Определяет порядок присваивания значений параметров:

pbByName — по именам параметров

pbByNumber — по номерам параметров в списке свойства Params

property ParamCount: Word;

Ro

Возвращает общее число параметров

property Params: ТРдrams;

Pb

Индексированный список параметров

property Prepared: Boolean;

Pu

Возвращает True, если подготовка процедуры уже проводилась

property StmtHandle: HDBIStmt;

Ro

Дескриптор выражения BDE. Используется при прямом вызове функций BDE

property StoredProcName: string;

Pb

Содержит имя хранимой процедуры

Методы

procedure CopyParams (Value: TPa rams) ;

Pu

Копирует параметры из списка value

function DescriptionsAvailabIa: Boolean;

Pu

При значении True параметры хранимой процедуры доступны из приложения

procedure ExecProc;

Pu

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

procedure GetResults;

Pu

Возвращает выходные параметры в приложение (используется только для сервера Sybase)

function ParamByName (const. Value: string): TParam;

Pu

Возвращает параметр с именем Value

procedure Prepare;

Pu

Готовит процедуру к выполнению

procedure UnPrepare;

Pu

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

Триггеры

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

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

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

Предположим, что при удалении записи из таблицы необходимо проверять наличие записей в другой таблице, связанных с удаляемой записью внешним ключом. (Такого рода операции встречаются в приложениях БД очень часто.) С выбранными таким образом записями требуется что-либо сделать (модифицировать, удалить, перенести и т. д.).

При помощи запросов SQL это можно сделать примерно так:

with Queryl do begin SQL.Clear;

SQL.Add('SELECT PartNo FROM Parts WHERE PartNo >= :P1');

Parains [0] .Aslnleger := SpinEditI.Value;

Open;

Query2.SQL.Clear;

Query2.SQL.Add('DELETE FROM Orders WHERE PartNo = :P1');

while Not EOf do begin

Query2.ParamsГО].Aslnteger := Fields[0].Aslnteger;

Query2.ExecSQL;

Next;

end;

Close;

end;

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

Queryl.SQL.Clear;

Queryl.SQL.Add('DELETE FROM Parts WHERE. PartNo >= :P1');

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

При создании логики триггера допускается применение операторов модификации данных (операторы delete, insert, update), но не отбора (оператор select можно использовать только для выбора записей, которые затем должны быть модифицированы).

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

Создание триггеров для сервера InterBase

Для создания триггеров используется оператор create trigger. Простой триггер для сервера InterBase, обеспечивающий сохранение ссылочной целостности, выглядит следующим образом:

CREATE TRIGGER PartsRE FOR Vendors BEFORE DELETE AS BEGIN

DELETE FROM Parts

WHERE Vendors.VenNo=Parts.VenNo ;

END

Тип события, на которое реагирует триггер, определяется операторами

BEFORE DELETE, AFTER DELETE. BEFORE INSERT, AFTER INSERT, BEFORE UPDATE, AFTER UPDATE.

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

CREATE TRIGGER GET_NEW_VALUE FOR Vendors BEFORE INSERT AS BEGIN NEW.ItemIdx = GEN_ID(InItem_Gen, 1);

END

В этом примере для создания нового уникального значения используется генератор initem_Gen.

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

Кэширование данных

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

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

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

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

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

Эти критерии могут применяться в различных сочетаниях.

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

Свойство UpdateRecordTypes задает типы модифицированных записей, которые доступны в кэшируемом наборе данных. По умолчанию видны измененные, добавленные и неизмененные записи. К ним можно добавить удаленные записи. Эти типы можно сочетать в любых комбинациях. О типе текущей записи расскажет метод updateStatus.

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

Чаще всего методы AppiyUpdates и commitupdates используются парой.

О наличии в кэше еще не зафиксированных изменений говорит свойство updatespending. При значении True кэш еще содержит несохраненные изменения.

Отмена изменений для текущей записи осуществляется методом RevertRecord.

Небольшой пример осуществляет отмену удаления старых и добавления новых записей:

with Queryl do begin

UpdateRecordTypes := [rtDeleted];

First;

while Not EOF do

begin RevertRecord;

Next;

end;

UpdateRecordTypes := [rtModified, rtlnserted, rtUnmodified];

First;

Edit;

while Not EOF do

if UpdateStatus = usinserted then Delete

else Next;

AppiyUpdates;

CommitUpdates;

end;

Сначала восстанавливаются все удаленные данные. Для этого при помощи свойства UpdateRecordTypes в наборе данных остаются для просмотра только удаленные записи. Затем эти записи последовательно восстанавливаются методом RevertRecord.

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

В завершение все сделанные изменения фиксируются на сервере и кэш очищается.

Для управления локальным кэшированием предназначены методы-обработчики OnUpdateRecord И OnUpdateError набора данных.

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

type

TOpdateAction = (uaFaii, uaAbort, uaSkip, uaRetry, uaApplied);

TOpdateRecordEvent - procedure(DataSet: TDataSet; UpdateKind: TUp-dateKind; var UpdateAction: TUpdateActlon) of object;

property OnUpddLeRecord: TUpdateRecordEvent;

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

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

Метод OnUpdateError вызывается при возникновении ошибки фиксации данных.

type

TOpdateAction = (uaFail, uaAbort, uaSkip, uaRetry, uaApplied);

TUpdateErrorEvent - procedure(DataSet: TDataSet; E: EDatabaseError;

UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction). of object;

property OnUpdateError: TUpdateErrorEvent;

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

Механизм кэширования изменений может использоваться и при работе с полями. Компонент TField имеет свойства Oldvalue и Newvalue, которые хранят соответственно старое и новое значение поля. Старое значение считывается из базы данных, новое значение содержит величину, которая будет записана на сервер при фиксации изменений. Эти свойства очень полезны при создании кода для рассмотренных выше методов-обработчиков.

Перенос данных

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

Для решения подобного рода задач в VCL Delphi имеется компонент TuatchMove, который инкапсулирует функции работы с группами записей для двух наборов данных. Кроме этого, компонент TTabie имеет метод BatchMove, который выполняет операцию, определяемую параметром AMode, по переносу всех записей из набора данных в указанный в параметре ASource набор данных.

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

Таблица 22.2. Свойства компонента TBatchMove

Объявление

Тип

Описание

property AbortOnKeyViol: Boolean;

Pb

При значении True операция переноса данных прерывается после возникновения ошибки уникальности индекса или нарушения ссылочной целостности

property AbortOnProblem: Boolean;

Pb

При значении True операция переноса данных прерывается при возникновении ошибки совместимости данных

property ChangedCount: Integer;

Ro

Содержит число записей приемника данных, над которыми выполнена операция переноса

property ChangedTableName: TfileName;

Pb

Задает таблицу в формате Paradox, в которой будут сохранены записи приемника данных до их изменения

property CommitCount: Integer;

Pb

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

property Destination:

TTabie;

Pb

Ссылка на набор данных, куда переносятся данные (приемник)

property KeyViolCount: Longint;

Ro

Содержит число записей, которые не попали в приемник данных вследствие ошибки индексации

property KeyViolTableName: TTable;

Pb

Задает таблицу в формате Paradox, в которой будут сохранены записи источника данных, которые не были обработаны вследствие ошибки индексации

property Mappings: rstrings;

Pb

Содержит карту переноса данных между отдельными полями источника и приемника данных

property Mode: TbaLchMode;

Pb

Определяет тип операции

property MuvedCount: Longint;

Ro

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

property ProblemCount: Longint;

Ro

Содержит число записей, которые не попали в приемник из-за ошибок совместимости типов

property ProblemTableName: TTable;

Pb

Задает таблицу в формате Paradox, в которой будут сохранены записи источника данных, которые не были обработаны вследствие ошибки совместимости типов

property RecordCount: Longint;

Pb

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

property Source: TBDEDataSet;

Pb

Ссылка на набор данных, откуда переносятся данные (источник)

property Transliterate: Boolean;

Pb

При значении True перенос данных осуществляется с учетом языковых драйверов источника и приемника

Компонент переноса данных может работать только с двумя компонентами TTable, которые заданы в свойствах source (источник данных) и Destination (приемник данных).

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

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

По умолчанию при выполнении операции подразумевается, что структуры данных таблиц совпадают, то есть данные из первого поля источника будут переноситься в первое поле приемника, второе — во второе и т. д. Для организации произвольного порядка переноса данных между полями используется поле Mapping типа TStrinqa. В нем одна строка отводится на связывание двух полей: одного из источника и одного из приемника. Если поля имеют одинаковые имена, то достаточно просто указать это имя. Если имена полей различаются, то между ними ставится знак равенства. Имена полей приемника пишутся слева.

При помощи свойств ProblemTableName И KeyViolTableName можно задать имена таблиц в формате Paradox, в которых будут сохранены все записи, не перенесенные по какой-либо причине в приемник данных. Свойство changedTabieName определяет таблицу для сохранения записей приемника до их обновления при переносе данных.

Компонент TUpdateSQL

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

Основу компонента составляют три экземпляра класса TQuery, которые создаются во время выполнения. Каждый из них предназначен для реализации одного типа запроса. Это запросы модификации (оператор update), удаления (оператор delete) и добавления данных (оператор insert). Именно эти запросы являются основой механизма редактирования немодифицируемых наборов данных. Тексты запросов содержатся в свойствах Modify sql, InsertSQL И DeleteSQL.

Как же связать компонент с немодифицируемым набором данных с компонентом TUpdateSQL? свойство UpdateObject Компонента TTable или TQuery

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

Свойство UpdateObject позволяет связать один набор данных с одним компонентом TUpdateSQL. Если для одного набора данных требуется использовать несколько компонентов TUpdateSQL, то для связи можно применить свойство DataSet компонента TUpdateSQL (табл. 22.3).

Кэширование изменений обеспечивает сохранение старых (неизмененных) значений полей, которые необходимы для работы компонента TUpdateSQL.

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

Перед выполнением запросов, в компонент TUpdateSQL необходимо передать текущие значения модифицируемых полей. Для этого используется метод setparams. Выполнение запросов осуществляется методом ExecSQL. Обе эти операции одновременно выполняет метод Apply.

Одновременно может выполняться только один из трех запросов. Для идентификации типа запроса служит одно из трех значений типа TUpdateKind:

Таблица 22.3. Свойства и методы компонента TUpda teSQL

Объявление

Тип

Описание

Свойства

property DataSet: TDataSet;

Pu

Определяет связанный с компонентом набор данных

property DeleteSQL: TStrings;

Pb

Содержит текст запроса удаления данных

property InsertSQL: TStrings;

Pb

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

properLy ModlfySQL: TStrings;

Pb

Содержит текст запроса модификации данных

property Query [UpdateKind: TUpdateKind]: TQuery;

Ro

Возвращает экземпляр объекта TQuery, типа UpdateKind

property SQL [UpdateKind: TUpdateKind]: TStrings;

Pu

Возвращает текст запроса типа UpdateKind

Методы

procedure Apply (UpdateKind: TUpdateKind);

Pu

Устанавливает параметры и выполняет запрос

типа UpdateKind

Свойства

procedure ExecSQL (UpdateKind: TUpdateKind) ;

Pu

Выполняет запрос типа UpdateKind

procedure SetParams (UpdateKind: TUpdateKind);

Pu

Устанавливает параметры запроса типа

UpdateKind

Подготовка компонента TQuery для использования совместно с компонентом TUpdateSQL включает следующие этапы:

  1. Результат выполнения запроса должен быть редактируемым. Свойство RequestLive должно иметь значение True.
  2. Должен быть включен механизм кэширования изменений. Свойство

cachedupdates должно иметь значение True.

С компонентом запроса связывается компонент TUpdateSQL. Свойство

UpdateObject должно указывать на компонент UpdateQuery.

В запросах компонента UpdateQuery имеются две особенности.

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

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

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

ModifyS.QL):

UPDATE COUNTRY SET

COUNTRY = :COUNTRY,

CURRENCY = :CURRENCY WHERE

COUNTRY = :OLD_COUNTRY

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

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

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

InsertSQL):

INSERT INTO COUNTRY

(COUNTRY, CURRENCY) VALUES

(:COUNTRY, :CURRENCY)

Текст запроса удаления данных имеет следующий вид (свойство DeieteSQL):

DELETE FROM COUNTRY WHERE

COUNTRY = :COUNTRY AND

CURRENCY = :CURRENCY

Резюме

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

Материал этой главы основан на следующих главах: