Книга: Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/FireBird/Yaffil
Поддержка array-полей. Пример использования TpFIBUpdateObject и TDataSetContainer
Поддержка array-полей. Пример использования TpFIBUpdateObject и TDataSetContainer
InterBase с самых ранних версий позволял описывать в таблицах многомерные поля-массивы, делая хранение специализированных данных максимально удобным. Вы наверняка согласитесь, что матриц) проще всего хранить и обрабатывать в виде матрицы, а не раскладывать ее по отдельным полям и даже таблицам из-за ограничений реляционной модели Тем не менее поскольку array- поля не поддерживаются стандартом SQL, то и работа с такими полями на уровне SQL-запросов крайне затруднена. Фактически вы можете использовать массивы только поэлементно и только в операциях чтения Чтобы изменить значения array-поля, необходимо использовать специальные команды InterBase API. FIBPlus позволяет обойтись без подобных сложностей, взяв на себя всю рутину, связанную с array-полями
Мы продемонстрируем, как работать с array-полями при помощи FIBPlus на примере DemoArray5, входящем в стандартную поставку FIBPlus. Пример демонстрирует два варианта использования array-полей Первый способ позволяет редактировать array-поле при помощи специальных методов ArrayFieldValue и SetAiiayValue и работать с таким полем как с единой структурой (рис 2 6П
Рис 2.61. Внешний вид формы примера DemoArrayS Первый вариант использования array—полей
Рассмотрим запросы, заданные в соответствующих свойствах ArrayDataSet:
SeleetSQL
SELECT
JOB JOB_CODE,
JOB.JOB_GRADE,
JOB.JOB_COUNTRY,
JOB.JOB_TITLE,
JOB MIN_SALARY,
JOB.MAX_SALARY,
JOB.JOB_REQUIREMENT,
JOB.LANGUAGE_REQ
FROM
JOB JOB
ORDER BY 1,2,3
UpdateSQL:
UPDATE JOB SET
JOB_CODE = ?JOB_CODE,
JOB_GRADE = ?JOB_GRADE,
JOB_COUNTRY = ?JOB_COUNTRY,
JOB_TITLE = ?JOB_TITLE,
MIN_SALARY = ?MIN_SALARY,
MAX_SALARY = ?MAX_SALARY,
JOB_REQUIREMENT = ?JOB_REQUIREMENT,
JOB.LANGUAGE_REQ= ?LANGUAGE_REQ WHERE
JOB_CODE = ?OLD_JOB_CODE
and JOB_GRADE = ?OLD_JOB_GRADE
and JOB_COUNTRY = ?OLD_JOB_COUNTRY
InsertSQL
INSERT INTO JOB(
JOB_CODE,
JOB_GRADE,
JOB_COUNTRY,
JOB_TITLE,
MIN_SALARY,
MAX_SALARY,
JOB_REQUIREMENT,
JOB.LANGUAGE_REQ
VALUES(
?JOB_CODE,
?JOB_GRADE,
?JOB_COUNTRY,
?JOB_TITLE,
?MIN_SALARY,
?MAX_SALARY,
?JOB_REQUIREMENT,
?LANGUAGE_REQ
DeleteSQL:
DELETE FROM JOB
WHERE
J03_CODE = ? OLD_JOB_CODE
and JOB_GRADE = ?OLD_JOB_GRADE
and JOB_COUNTRY = ?OLD_JOB_COUNTRY
RefreshSQL:
SELECT
JOB.JOB_CODE,
JOB.JOB_GRADE,
JOB.JOB_COUNTRY,
JOB.JOB_TITLE,
JOB.MIN_SALARY,
JOB.MAX_SALARY,
JOB.JOB_REQUIREMENT,
JOB.LANGUAGE_REQ
FROM
JOB JOB
WHERE
JOB.JOB_CODE = ?OLD_JOB_CODE
and JOB.JOB_GRADE = ?OLD_JOB_GRADE
and JOB.JOB_COUNTRY = ?OLD_JOB_COUNTRY
Поле LANGUAGE_REQ является массивом (LANGUAGE_REQ VARCHAR(15) [1:5]) и, как видно из запросов, обрабатывается целиком, а не поэлементно. С одной стороны это удобно, но не позволяет использовать для редактирования таких полей специализированные визуальные компоненты типа TDBGrid. Если мы используем array-поля для хранения значительных массивов данных, мы в любом случае будем использовать "ручную" обработку данных, не прибегая к помощи визуальных компонент. Однако для наглядности примера мы позволим редактировать элементы массива в компонентах TEdit.
Фактически нам понадобится написать только два основных обработчика для событий: BeforePost и OnPostError
procedure TForml.ArrayDataSetBeforePost(DataSet: TDataSet);
begin
with ArrayDataSet do begin
SetArrayValue(FieldByName('LANGUAGE_REQ'),
VarArrayOf([
Editl.Text,
Edit2.Text,
Edit3.Text,
Edit4.Text,
Edits.Text
]) ) ;
end;
end;
procedure TForml.ArrayDataSetPostError(DataSet: TDataSet;
E: EDatabaseError; var Action: TDataAction);
begin
Action := daAbort;
MessageDlg('Error!', mtError, [mbOk], 0);
ArrayDataSet.Refresh;
end;
Метод SetArrayValue позволяет задать все элементы поля в виде массива. Важным моментом является обработчик ошибки ArrayDataSetPostError В случае неудачной операции Update или Insert необходимо восстанавливать внутренний идентификатор массива у редактируемой записи. Это правило диктуется функциями InterBase API, и мы должны их придерживаться. Для восстановления идентификатора необходимо получить значения полей текущей записи заново, что и делается при помощи явного вызова метода Refresh.
Для автоматического заполнения визуальных компонентов значениями элементов массива мы можем написать обработчик события AfterScroll:
procedure TForml.ArrayDataSetAfterScroll(DataSet: TDataSet);
var v: Variant;
begin
with ArrayDataSet do try
FInShowArrays := true;
v := ArrayFieldValue(FieldByName('LANGUAGE_REQ'));
Editl.Text := VarToStr(v[l]);
Edit2.Text := VarToStr(v[2]);
Edit3.Text := VarToStr(v[3]);
Edit4.Text := VarToStr(v[4]);
EditS.Text := VarToStr(v[5]);
finally
FInShowArrays:=false;
end;
end;
Флаг FInShowArrays используется в примере для того, чтобы не включать режим редактирования записи при обычной навигации.
Рекомендуется иметь под руками полный текст примера, который доступен на сайте http://www.fibplus.net. В книге мы указываем только те части примера, которые имеют непосредственное и наибольшее значение для работ с array-полями. Однако без полного текста некоторые части исходного текста могут показаться не до конца наглядными.
Данный способ работы с array-полями нельзя применять в режиме CachedUpdates.
Второй способ работы с array-полями позволяет использовать их в "живых" запросах и редактировать при помощи стандартных визуальных компонентов (рис. 2.62).
Рассмотрим запросы, указанные в свойствах ArrayDataSet:
SelectSQL:
SELECT
JOB.JOB_CODE,
JOB.JOB_GRADE,
JOB.JOB_COUNTRY,
JOB.JOB_TITLE,
JOB.LANGUAGE_REQ[1] LQ1,
JOB.LANGUAGE_REQ[2] LQ2
FROM
JOB JOB
ORDER BY 1,2,3
Рис 2.62. Внешний вид формы примера DemoArrayS. Второй вариант использования array-полей
UpdateSQL:
UPDATE JOB SET
JOB_TITLE = -?JOB_TITLE,
JOB JOB_GRADE=?JOB_GRADE,
JOB.JOB_COUNTRY=?JOB_COUNTRY,
LANGUAGE_REQ = ''LQ
WHERE
JOB_CODE = ?OLD_JOB_CODE
and JOB_GRADE = ?OLD_JOB_GRADE
and JOB_COUNTRY = ?OLD_JOB_COUNTRY
InsertSQL:
INSERT INTO JOB(
JOB_CODE,
JOB_GRADE,
JOB_COUNTRY,
JOB_TITLE,
LANGUAGE_REQ
VALUES(
?JOB_CODE,
?JOB_GRADE,
?JOB_COUNTRY,
?JOB_TITLE,
?LQ
DeleteSQL:
DELETE FROM JOB
WHERE
JOB_CODE = ?OLD_JOB_CODE
and JOB_GRADE = ?OLD_JOB_GRADE
and JOB_COUNTRY = ?OLD_JOB_COUNTRY
RefieshSQL:
SELECT
JOB.JOB_CODE,
JOB.JOB_GRADE,
JOB.JOB_COUNTRY,
JOB.JOB_TITLE,
JOB.LANGUAGE_REQ[1] LQ1,
JOB.LANGUAGE_REQ[2] LQ2
FROM
JOB JOB
WHERE
JOB.JOB_CODE = ?OLD_JOB_CODE
and JOB. JOB_GRADE = ?OLD_JOB_GRADE
and JOB.JOB_COUNTRY = ?OLD_JOB_COUNTRY
На этот раз мы выбираем только два элемента из нашего поля-массива. Обратите внимание' несмотря на то что в выбирающем запросе мы явным образом выделяем два элемента массива, а в модифицирующих запросах мы обновляем поле целиком На самом деле, это, конечно, не совсем так, однако данный синтаксис наиболее удобен и близок к естественному SQL-запросу несмотря на то что во внутренней реализации FIBPlus использует специальные функции работы с массивами Обратим внимание на компонент QryAirField TpFIBUpdateObject. Именно он позволит нам правильно сформировать значение параметра "LQ" в модифицирующих запросах Разумеется, для этой цели вполне бы подошел и простой TpFIBQuery, однако удобство TpFIBUpdateObject заключается в первую очередь в том, что он выполняет запрос автоматически и сам подставляет туда нужные значения параметров в зависимости от значений TpFIBDataSet. Вот запрос, который выполняет QryArrField SELECT
JOB LANGUAGE_REQ,
JOB JOB_CODE
FROM
JOB JOB
WHERE
JOB JOB_CODE=?OLD_JOB_CODE and
JOB.JOB_GRADE=?OLD_JOB_GRADE and
JOB.JOB_COUNTRY=?OLD_JOB_COUNTRY
Обратим внимание на свойства QryArrField (рис 2 63)
Рис 2.63. Свойство компонента QryArrField
Обработчик QiyAnField AtteiExecute
procedure TForm2 QryArrFieldAfterExecute(Sender: TObject);
var v Variant,
begin
v = QryArrField Fields[0] GetArrayValues,
with ArrayDataSet do begin
v[l] = FieldByName('LQ1') AsString,
v[2] = FieldByName( LQ2 ) AsString,
QryArrField Fields[0] SetArrayValue(v);
QUpdate Params ByName[ LQ ] AsQuad :=
QryArrField.Fields[0] AsQuad;
end;
QryArrField Close;
end;
Теперь все становится совершенно очевидным Поскотьку свойство QiyAnField KindUpdate равно ukModify, то QiyArrField выполняет запрос при изменении записи в AnayDataSet а поскольку свойство QiyArrField ExecuteOider равно eoBeforeDefault, то запрос (QiyArrField SQL) выполняется до того, как AirayDataSet выполнит свой собственный UpdateSQL В обработчике QryArrField AfterExecute мы всего лишь получаем заново все текущие элементы массива из базы данных, подменяем два из них новыми значениями, которые указал пользователь, и задаем значение параметра для ArrayDataSet.UpdateSQL Это означает что при выполнении ArrayDataSet.UpdateSQL формально будут обновлены все пять элементов массива но фактически изменены значения тотько двух элементов, которые изменил пользователь в TDBGrid.
Как видите работа с массивами достаточно проста поскотьку все основные сложности решают компоненты FIBPlus Хотелось бы также рассмотреть еще один специализированный компонент, входящий в FIBPlus Пример DemoArray демонстрирует работу с TDataSetContamei, использованным для синхронизации значений двух TpFIBDataSet, редактирующих наше апау-поле (рис 2 64)
Рис 2.64. Использование TDatabetContamer
Компонент DataSetContamerl помещен вместе с Database и Transaction на DataModule в нашем приложении Оба компонента AnayDataSet из разных форм нашего приложения ссылаются на DataSetContamerl при помощи свойства Contamei (рис 2 65)
Рис 2.65. Подключение компонентов TpFIBDataSet к DataSetContamerl
Компонент TDataSetContamei позволяет централизованно обрабатывать события от разных компонентов TpFIBDataSet, а также (расширяя, таким образом, список стандартных событий) посылать им сообщения, при получении которых они могут производить какие-то дополнительные действия. В нашем примере DataSetContainerl имеет обработчики двух событий OnDataSetEvent и OnUserEvent
procedure TDataModule2.DataSetsContainerlDataSetEvent(DataSet:
TDataSet;
Event: TKindDataSetEvent);
var Info: string;
begin
if Event = deAfterPost then
if DataSet.Owner.Name = 'Forml' then
DataSetsContainerl.NotifyDataSets(DataSet,
'Form2.ArrayDataSet', 'JOB_TABLE_CHANGED', Info)
else
if DataSet.Owner.Name = 'Form2' then
DataSetsContainerl.NotifyDataSets(DataSet,
'Form1.ArrayDataSet', 'JOB_TABLE_CHANGED', Info);
end;
procedure TDataModule2.DataSetsContainerlUserEvent(Sender:
TObject;
Receiver: TDataSet; const EventName: String; var Info:
String) ;
begin
if EventName = 'JOB_TABLE_CHANGED' then begin
with TpFIBDataSet(Sender) do
if (not CachedUpdates) and
(not TpFIBDataSet(Receiver).CachedUpdates) then
if
TpFIBDataSet(Receiver).Locate('JOB_CODE;JOB_GRADE;JOB_COUNTRY', varArrayOf([
FieldByNamet'JOB_CODE').AsString,
FieldByName('JOB_GRADE').AsString,
FieldByName('JOB_COUNTRY').AsString
]), []) then TpFIBDataSet(Receiver) Refresh
end;
end;
Смысл действий сводится к следующему: после изменения записи в одном из наших компонентов ArrayDataSet происходит событие AfterPost. Поскольку DataSetContainerl перехватывает все события у подчиненных компонентов, то срабатывает обработчик OnDataSetEvent. Параметр Event равен deAfterPost, а параметр DataSet ссылается на тот компонент, в котором произошло изменение записи. При помощи вызова метода NotifyDataSets DataSetContainerl посылает сообщение оставшемуся компоненту ArrayDataSet о том, что произошло изменение записи. Поскольку оба компонента на самом деле редактируют одну и ту же таблицу, то желательно синхронизировать изменения. Синхронизация происходит в обработчике события OnUserEvent, то есть при получении "извещения" об изменении какого-либо из ArrayDataSet.
Если получено сообщение "JOB_TABLE_CHANGED" и ни в одном из наших двух ArrayDataSet не включен режим CachedUpdates (в этом случае Refresh просто ничего не даст), то мы позиционируемся на соответствующую запись и вызываем для нее метод Refresh, обновив, таким образом, запись в одном ArrayDataSet после того, как она была изменена в другом ArrayDataSet.
Данная технология является совершенно уникальной: использование DataSetContainer позволяет делать чрезвычайно гибкие и в то же время прозрачные схемы обработки и синхронизации данных в нескольких компонентах TpFIBDataSet.
- Обработка потери подключения к базе данных
- Эмуляция Boolean-полей
- Поддержка array-полей. Пример использования TpFIBUpdateObject и TDataSetContainer
- Работа с BLOB-полями
- Локальная сортировка и локальная фильтрация
- Обработка событий InterBase при помощи FIBPIus
- "Низкоуровневая" работа с внутренним буфером TpFIBDataSet
- Пример установочного скрипта
- Пример из практики
- ПРИМЕР ПРОСТОЙ ПРОГРАММЫ НА ЯЗЫКЕ СИ
- Примеры получения статистики
- Пример применения метода «пять почему»
- Поддержка SMP
- Пример 12-8. Частота встречаемости отдельных слов
- 1.2.5. Пример программы
- Пример 17-10. Блочный комментарий
- Примеры
- 2. Пример создания базового отношения в записи на псевдокоде
- Пример 9-8. Содержимое $* и $@, когда переменная $IFS -- пуста