Devrace FIBPlus. Особенности механизма master-detail при работе с Interbase

Devrace FIBPlus. Особенности механизма master-detail при работе с Interbase

Прежде, чем начать собственно изложение технических деталей, мне хотелось бы немного рассказать о FIBPlus. Это библиотека native-компонент для Bor-land Delphi и Borland C++ Builder, которая позволяет разработчику наиболее эффективно работать с базами данных Interbase и всех его клонов, используя Direct Interbase API. В общем-то, это означает, что при помощи FIBPlus разработчик может использовать все возможности Interbase: полное управление транзакциями, максимальную скорость, специализированные особенности Interbase (например, array-fields) и т.д. При всем этом компоненты FIBPlus абсолютно совместимы со стандартными db-aware-компонентами, что будет показано в процессе разработки демонстрационного приложения.

Использование связей master-detail является одной из самых распространенных задач для любого прикладного разработчика, который использует в своей работе реляционные базы данных. В общем-то, сам реляционный подход в оптимизации базы данных и означает создание все более глубоких связей master-detail. Таким образом, после оптимизации базы данных перед программистом встает необходимость максимально упростить себе жизнь при разработке пользовательского интерфейса, через который потом будет осуществляться работа со всеми master-detail-связками в его базе данных. Мы рассмотрим управление такой связкой на примере стандартной базы данных из поставки Interbase EMPLOYEE. GDB и двух таблиц: DEPARTMENT и EMPLOYEE. Ниже вы видите структуры обеих таблиц.

CREATE TABLE DEPARTMENT (
DEPT_NO DEPTNO NOT NULL,
DEPARTMENT VARCHAR (25) NOT NULL,
HEAD_DEPT DEPTNO,
MNGR_NO EMPNO,
BUDGET BUDGET,
LOCATION VARCHAR (15),
PHONE_NO PHONENUMBER DEFAULT '555-1234',
DEPT_NO1 CUSTNO);

ALTER TABLE DEPARTMENT ADD PRIMARY KEY (DEPT_NO);

CREATE TABLE EMPLOYEE (
EMP_NO EMPNO NOT NULL,
FIRST_NAME FIRSTNAME NOT NULL,
LAST_NAME LASTNAME NOT NULL,
PHONE_EXT VARCHAR (4),
HIRE_DATE DATE DEFAULT 'NOW' NOT NULL,
DEPT_NO DEPTNO NOT NULL,
JOB_CODE JOBCODE NOT NULL,
JOB_GRADE JOBGRADE NOT NULL,
JOB_COUNTRY COUNTRYNAME NOT NULL,
SALARY SALARY NOT NULL,
FULL_NAME COMPUTED BY (last_name || ', ' || first_name));
ALTER TABLE EMPLOYEE ADD PRIMARY KEY (EMP_NO);
ALTER TABLE EMPLOYEE ADD FOREIGN KEY (DEPT_NO) REFERENCES DEPARTMENT (DEPT_NO);
Очевидно, что таблица DEPARTMENT является master-таблицей, а таблица EMPLOYEE — detail-таблицей. Связка master-detail осуществляется по полям:
DEPARTMENT.DEPT_NO <- EMPLOYEE. DEPT_NO
То есть для каждого отдела мы можем сохранять информацию обо всех его сотрудниках.

Шаг 1. Получение данных из таблицы и вывод данных в TDBGrid.
Рассмотрим с самого начала создание приложения, при помощи которого мы сможем редактировать данные об отделах и сотрудниках. Необходимо бросить на форму основной компонент, который позволяет подключаться к базе данных Interbase (TpFIBDatabase) и вызывать редактор этого компонента:





Для подключения к базе необходимо как минимум указать путь (в данном примере это путь к локальному файлу), имя пользователя и пароль. Вы можете проверить правильность заданных параметров, нажав на кнопку Test. Мы также можем задать параметры подключения к базе в run-time:



procedure TForm1.Button1Click(Sender: TObject);
begin
pFIBDatabase1.DBName := DBNameE.Text;
pFIBDatabase1.ConnectParams.UserName := UserNameE.Text;
pFIBDatabase1.ConnectParams.Password := PasswordE.Text;
pFIBDatabase1.Open;
end;
Следующий этап — получение данных из базы данных. Для этого нам необходимо два компонента: TpFIBDataSet и TpFIBTransaction. TpFIBDataSet является потомком класса TDataSet и полностью совместим со всеми стандартными визуальными компонентами (Da-taSource1, в частности, связан с pFIBDataSet1). TpFIBTransaction предназначен для управления транзакцией.



Необходимо указать уровень изоляции транзакции при помощи свойства TPBMode, а также "подключить" pFIBTransaction1 к pFIBDatabase1:



Аналогично необходимо указать свойства Database и Transaction для компонента pFIBDataSet1:





Теперь мы можем указать необходимые запросы к базе данных для получения и изменения данных в таблице. Нужно написать запрос для получения данных, используя свойство SelectSQL. В нашем случае это будет очень простой запрос:
SELECT * FROM DEPARTMENT
Теперь изменим немного код в процедуре:
procedure TForm1.Button1Click(Sender: TObject);
begin
pFIBDatabase1.DBName := DBNameE.Text;
pFIBDatabase1.ConnectParams.UserName := UserNameE.Text;
pFIBDatabase1.ConnectParams.Password := PasswordE.Text;
pFIBDatabase1.Open;

pFIBDataSet1.Open;
end;
Нам не нужно явным образом запускать транзакцию перед открытием запроса, поскольку по умолчанию свойство pFIBDataSet1.Options содержит ключ poStartTransaction, и компонент сам запускает транзакцию. После запуска приложения и открытия базы данных мы увидим следующий результат:




Шаг 2. Создание "живых" запросов. Использование генераторов для получения значений Primary Key.
Если вы попробуете исправить какие-то значения в DBGrid1, то увидите, что в текущем состоянии мы имеем read-only-запрос. Для того, чтобы наш запрос стал редактируемым, или "живым", нам надо написать несколько дополнительных запросов, которые будут автоматически выполняться компонентом pFIBDataSet1 при редактировании данных в DBGrid1. Необходимо заполнить свойства:



FIBPlus включает специальный design-time-редактор для редактирования и генерации модифицирующих запросов. Вы можете вызвать Generator SQLs из контекстного меню, если нажмете правой кнопкой мыши на компоненте pFIBDataSet1.



Для генерации модифицирующих запросов необходимо сначала указать основную таблицу в списке. Если в запросе участвует только одна таблица, то она будет выбрана в списке автоматически. После этого необходимо нажать кнопку Get Table Fields для заполнения списков Key Fields и Update Fields. В первом из них нужно выделить те поля, которые будут вставлены в условие WHERE во всех модифицирующих запросах. В списке Update Fields необходимо выделить те поля, которые мы хотим редактировать. По умолчанию выделены все поля таблицы DEPARTMENT. Теперь достаточно нажать кнопку Generate SQLs и получить автоматически все модифицирующие запросы, которые вы можете увидеть на закладке SQLs. Например, для свойства Insert-SQL мы получим следующий запрос:
INSERT INTO DEPARTMENT(
DEPT_NO, DEPARTMENT, HEAD_DEPT, MNGR_NO, BUDGET, LOCATION,
PHONE_NO
)
VALUES(
?DEPT_NO, ?DEPARTMENT, ?HEAD_ DEPT, ?MNGR_NO, ?BUDGET, ?LOCATION,
?PHONE_NO
)
Обратите внимание также на запрос для свойства Re-freshSQL. Это такой же выбирающий запрос, но возвращать он должен только одну запись — текущую! Теперь, после создания всех запросов, когда пользователь вставит новую запись в DBGrid1, pFIBDataSet1 автоматически заполнит значения параметров ?DEPT_NO, ?DEPARTMENT и т.д. теми значениями полей, которые наберет пользователь. Обратите внимание на поле DEPT_NO. Это целочисленное поле, которое является primary key для таблицы и должно содержать уникальные значения. Interbase предлагает специальную возможность для получения таких номеров, гарантируя уникальность в масштабе базы данных, — генераторы. Особенность использования генераторов состоит в том, что мы должны получать новое значение генератора в приложении до того, как выполним запрос на вставку записи. Зачем это нужно? Дело в том, что после выполнения любого модифицирующего запроса (кроме удаления) pFIBData Set1 автоматически выполняет запрос из Re-freshSQL, подставляя в качестве условия текущие значения параметров. В нашем случае для подстановки надо использовать значение первичного ключа. Если мы не получим его заранее, а будем генерировать его, используя триггер, то не сможем подставить значение DEPT_NO в запрос Refresh-SQL, а значит, и перечитать измененную запись. Таким образом, если какие-то поля записи менялись внутри триггеров базы данных, то мы не увидим эти изменения, пока не переоткроем весь запрос целиком. Если же мы сначала получим новое значение генератора, а потом вставим это значение наравне с остальными параметрами, то впоследствии сможем использовать это же значение для refresh'а текущей записи и будем "в курсе" актуальных значений полей без излишних переоткрываний!

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



Прежде всего, нужно указать название таблицы DEPARTMENT, название ключевого поля DEPT_NO и название генератора DEPT_NO_ GEN. Свойство WhenGetGenID может принимать три возможных значения:
• wgOnNewRecord — получать значение генератора сразу после подготовки буфера для новой записи;
• wgBeforePost — получать значение генератора непосредственно перед отправкой новой записи на сервер;
• wgNever — не использовать механизм генерации ключевых значений.
В нашем примере мы укажем значение wgOnNewRecord, чтобы сразу видеть в DBGrid1 те значения поля DEPT_NO, которые будут получены с сервера. Теперь мы можем запустить наше приложение и отредактировать какие-либо существующие записи и даже вставить новые записи:



Из рисунка видно, что было автоматически получено значение для поля DEPT_NO, равное 22. Вдобавок обратите внимание на тот факт, что значения по умолчанию для полей BUDGET и PHONE_NO были получены также автоматически.

Шаг 3. Режим AutoCommit. Работа в контексте двух транзакций для избежания DEADLOCK.
pFIBDataSet позволяет автоматически подтверждать сделанные изменения, если задать свойство AutoCommit в True. Теперь после вызова метода Post pFIBDataSet1 автоматически будет вызывать pFIBTrans-action1.CommitRetaining, сохраняя сделанные пользователем изменения и не закрывая при этом сам запрос. Такой подход уже снижает вероятность Deadlock, который возможен при наличии длинных не закрытых транзакций, в контексте которых производились изменения данных.
Тем не менее, FIBPlus предлагает другую возможность, которая сводит вероятность возникновения Deadlock практически к нулю. TpFIBDataSet может работать одновременно в контексте двух транзакций. Это одна длинная транзакция, в контексте которой данные только читаются, и другая короткая транзакция, в контексте которой выполняются все модифицирующие запросы.
Переименуем pFIBTrans-action1 в ReadTransaction и добавим еще один компонент WriteTransaction. После этого зададим свойство Update-Transaction у pFIBDataSet1 в WriteTransaction. Таким образом, теперь pFIBDataSet1 подключен сразу к двум компонентам TpFIBTransaction:



Теперь, после вызова метода Post, pFIBDataSet1 будет вызывать метод Commit у компонента WriteTransaction. Однако, учитывая, что данные читаются в контексте совсем другой транзакции (ReadTrans-action), это не вызовет закрытия pFIBDataSet1. То есть, используя FIBPlus, мы имеем настоящий режим AutoCommit, который уменьшает вероятность Deadlock и не мешает нам видеть актуальные значения всех записей. Вы наверняка знаете, что при использовании BDE в режиме Auto-Commit нельзя узнать реальные значения записей, не переоткрыв запрос.

Шаг 4. Связка master-detail. Использование специального префикса MAS_ для наименования параметров.
Теперь мы можем перейти к созданию собственно связки master-detail. Для этого положим на форму компоненты:
DBGrid2: TDBGrid;
DataSource2: TDataSource;
pFIBDataSet2: TpFIBDataSet;
ReadTransaction2: TpFIBTransaction;
WriteTransaction2: TpFIBTransaction;
"Свяжем" их так же, как и предыдущую группу компонентов. Напишем SelectSQL для pFIBDataSet2:
SELECT * FROM EMPLOYEE
WHERE DEPT_NO = ?DEPT_NO
Очевидно, что мы хотим выбрать при помощи detail-запроса только тех сотрудников, которые работают в текущем отделе. Значение параметра ?DEPT_NO должно браться из поля DEPT_NO таблицы DE-PARTMENT. Чтобы это происходило автоматически, необходимо задать свойство pFIB-DataSet2.DataSource равным DataSource1. Теперь сгенерируем модифицирующие запросы для pFIBDataSet2 так же, как мы делали это раньше для pFIBDataSet1.
После автоматической генерации мы должны внести некоторые изменения в полученные запросы. Рассмотрим, в частности, запрос для InsertSQL:
INSERT INTO EMPLOYEE(
EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, HIRE_DATE,
DEPT_NO,
JOB_CODE, JOB_GRADE, JOB_COUNTRY, SALARY
)
VALUES(
?EMP_NO, ?FIRST_NAME, ?LAST_NAME, ?PHONE_EXT, ?HIRE_DATE,
?DEPT_NO,
?JOB_CODE, ?JOB_GRADE, ?JOB_COUNTRY, ?SALARY
)
Очевидно, что при добавлении нового сотрудника мы должны задать параметр ?DEPT_NO текущим значением поля DEPT_NO из таблицы DEPARTMENT. FIBPlus позволяет делать это автоматически при помощи префикса MAS_:
INSERT INTO EMPLOYEE(
EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, HIRE_DATE,
DEPT_NO,
JOB_CODE, JOB_GRADE, JOB_COUNTRY, SALARY
)
VALUES(
?EMP_NO, ?FIRST_NAME, ?LAST_NAME, ?PHONE_EXT, ?HIRE_DATE,
?MAS_DEPT_NO,
?JOB_CODE, ?JOB_GRADE, ?JOB_COUNTRY, ?SALARY
)
Теперь мы явным образом указали, что значение для поля EMPLOYEE.DEPT_NO нужно брать из поля DEPT_NO таблицы DEPARMENT, которая является master-таблицей для таблицы EMPLOYEE. То же изменение необходимо внести в запрос UpdateSQL:
UPDATE EMPLOYEE SET
FIRST_NAME = ?FIRST_NAME,
LAST_NAME = ?LAST_NAME,
PHONE_EXT = ?PHONE_EXT,
HIRE_DATE = ?HIRE_DATE,
DEPT_NO = ?MAS_DEPT_NO,
JOB_CODE = ?JOB_CODE,
JOB_GRADE = ?JOB_GRADE,
JOB_COUNTRY = ?JOB_COUNTRY,
SALARY = ?SALARY
WHERE
EMP_NO = ?OLD_EMP_NO
Теперь изменим немного код в процедуре:
procedure TForm1.Button1Click(Sender: TObject);
begin
pFIBDatabase1.DBName := DBNameE.Text;
pFIBDatabase1.ConnectParams.UserName := UserNameE.Text;
pFIBDatabase1.ConnectParams.Password := PasswordE.Text;
pFIBDatabase1.Open;
pFIBDataSet1.Open;
pFIBDataSet2.Open;
end;
Можно запустить приложение. Двигаясь вверх и вниз по DBGrid1, вы можете наблюдать, как автоматически изменяется содержимое в DBGrid2. Связка master-detail уже работает.



ШАГ 5. Настройка механизма master-detail.
FIBPlus позволяет также настраивать некоторые особенности работы механизма master-detail. В частности, как вы уже обратили внимание, в нашем примере мы открывали detail-dataset (pFIBDataSet2) вручную. В случае с простой связкой это не трудно, однако, если мы используем несколько цепочек master-detail или более длинные цепочки — master-detail-subdetail, то ручное открытие всех запросов может даже привести к ошибке. Вы всегда должны открывать подчиненный запрос после основного. Чтобы избежать ненужного и совершенно очевидного кодирования, TpFIBDataSet содержит специальное свойство DetailOptions:



Задав ключ dcForceOpen, мы можем быть совершенно уверены, что detail-запрос будет автоматически открыт после открытия master-запроса. Теперь мы можем удалить строку pFIBDataSet2.Open из нашей программы. Какова бы ни была глубина связок master-detail, все запросы будут открыты в нужном порядке.
Вторая важная опция — ключ dcWaitEndMasterScroll. Представьте, что пользователь передвигается по DBGrid1, пытаясь найти нужный отдел. При каждом движении, когда меняется текущая запись в DBGrid1, pFIBDataSet2 автоматически переоткрывает запрос. Очевидно, движение по master-таблице может вызвать серию довольно "тяжелых" запросов, которые значительно увеличат совершенно бесполезный сетевой трафик. На практике было нужно переоткрыть detail-запрос только один раз, когда пользователь все-таки нашел нужный ему отдел в DBGrid1. FIBPlus позволяет избежать ненужных запросов. Если ключ dcWaitEndMaster Scroll добавлен в DetailConditions, то detail-запрос переоткрывается только после некоторой паузы (величину паузы можно регулировать, задавая значение свойства WaitEndMasterInterval).
Таким образом, когда пользователь просто передвигается по DBGrid1, изменение текущей записи "включает" таймер detail-запроса. Если за время паузы пользователь успевает перейти на другую запись, то таймер сбрасывается. И так до тех пор, пока пользователь не остановится на нужной записи. Только после этого detail-запрос переоткрывается, а значит, pFIBData-Set2 выполняет только один запрос вместо целой серии.
Вы увидели, каким образом построена работа со связями master-detail в FIBPlus: как подключаться к базе данных, как выполнять выбирающий запрос и генерировать модифицирующие, как работать с данными в контексте двух транзакций и связывать между собой master- и detail-датасеты. Несмотря на простоту описываемых подходов, все-таки рекомендуется внимательно изучить примеры для статьи.
Найти последнюю версию FIBPlus можно на сайтах http://www.devrace.com или http://www.devrace.de .

Сергей Востриков, Денис Мигачев
Более подробную информацию можно найти на сайте http://interbase-world.com



Компьютерная газета. Статья была опубликована в номере 20 за 2004 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета