Мир InterBase. Архитектура, администрирование и разработка приложений баз данных в InterBase/FireBird/Yaffil
Шрифт:
INSERT INTO "Goods"(
"Id" ,
"Name",
"Price",
"IdCategory" )
VALUES(
:"Id",
:"Name",
:"Price",
:"MAS_Id"
)
Теперь мы указали явным образом, что значение для поля "Goods"."IdCategory" нужно брать из поля "Id" таблицы "Categories", которая является master- таблицей для таблицы "Goods". To же изменение необходимо внести в запрос UpdateSQL
UPDATE "Goods" SET
"Id" = :"Id",
"Name" = :"Name",
"Price" = :"Price",
"IdCategory" = :"MAS_Id"
WHERE
"Id" = 'OLD_Id"
Теперь
procedure TMainForm.FormCreate(Sender: TObject);
begin
with TimFile. Create (' ib_price. mi ') do begin
pFIBDatabasel.DBName := ReadStringt'Options', 'DBPath',
'C:\IBPRICE.GDB');
Free;
end;
pFIBDatabasel.Open;
CategoriesDataSet.Open;
GoodsDataSet.Open;
end;
He забудем заполнить свойства AutoUpdateOptions, чтобы GoodsDataSet автоматически генерировал значения первичного ключа (рис 2 27)
Можно запустить приложение Двигаясь вверх и вниз по CategoriesGrid, вы можете наблюдать, как автоматически изменяется содержимое в GoodsGrid Связка master-detail уже работает.
FIBPlus позволяет также настраивать некоторые особенности работы механизма master-detail. В частности, как вы уже обратили внимание, в нашем примере мы открывали detail-dataset (GoodsDataSet) "вручную", при помощи явного вызова метода Open В случае с простой связкой это нетрудно, однако если мы используем несколько цепочек master-detail или более длинные цепочки - master-detail-subdetail, то ручное открытие всех запросов может даже привести к ошибке. Вы всегда должны открывать подчиненный запрос после основного.
Чтобы избежать ненужного и совершенно очевидного кодирования, TpFEBDataSet содержит специальное свойство DetailConditions (рис. 2.28).
Рис 2.27. Использование AutoUpdateOptions длл генерации значений первичного ключа GoodsDdtabet
Рис 2.28. Свойаво DetailConditions
Задав ключ dcForceOpen. мы можем быть совершенно уверены, что detail- запрос будет автоматически открыт после открытия master-запроса. Теперь мы можем удалить строку GoodsDataSet.Open из метода CreateForm. Какова бы ни была глубина связок master-detail, все запросы будут открыты в нужном порядке.
Вторая важная опция - это ключ dcWaitEndMastei Scroll Представьте, что пользователь передвигается по CategonesGrid, пытаясь найти нужную категорию товаров. При каждом движении, когда меняется текущая запись в CategoriesGrid, GoodsDataSet автоматически открывает запрос заново. Очевидно, движение по mastei-таблице может вызвать серию довольно "тяжелых" запросов, которые значительно увеличат совершенно бесполезный сетевой трафик. На практике было нужно переоткрыть detail-запрос только один раз, когда пользователь все-таки нашел нужную ему категорию в CategoriesGrid. FIBPlus
Таким образом, когда пользователь просто передвигается по CategoriesGrid, изменение текущей записи "включает" таймер detail-запроса. Если за время паузы пользователь успевает перейти на другую запись, то таймер сбрасывается. И так до тех пор, пока пользователь не остановится на нужной записи. Только после этого detail-запрос переоткрывается, а значит, GoodsDataSet выполняет только один запрос вместо целой серии.
Третий важный ключ в DetailConditions управляет поведением master- таблицы при изменении detail-таблицы. В нашем примере в таблице "Categories" существует поле "GoodsCount", в котором содержится количество наименований товаров для текущей категории. Когда мы добавляем или удаляем наименование товара из категории, то это значение должно меняться. Очевидно, что данный механизм легко реализуется парой триггеров для таблицы "Goods":
CREATE TRIGGER "Goods_AI" FOR "Goods" ACTIVE
AFTER INSERT POSITION 0
AS
BEGIN
update "Categories" set
"GoodsCount" = (select count("Id") from "Goods" where
"IdCategory" = New."IdCategory")
where "Id" = New."IdCategory";
END^
CREATE TRIGGER "Goods_BD" FOR "Goods" ACTIVE
BEFORE DELETE POSITION 0
AS
BEGIN
update "Categories" set
"GoodsCount" = (select count("Id") from "Goods" where
"IdCategory" = Old."IdCategory")
where "Id" = Old."IdCategory";
END^
Давайте рассмотрим последовательность происходящих действий, например при добавлении нового наименования товара для какой-то категории.
В GoodsDataSet формируется значение ключевого поля "Id" при помощи генератора.
Значения полей заполняются пользователем.
После нажатия пользователем клавиши Enter GoodsData подготавливает запрос из свойства InsertSQL к выполнению и заполняет значения параметров соответствующими значениями полей.
Параметр :"IdCategory" заполняется текущим значением поля "Id" из CategoriesDataSet.
Запрос выполняется и выполняется триггер "Goods_AI".
Транзакция GoodsWriteTransaction автоматически подтверждает сделанные изменения при помощи метода Commit.
Выполняется запрос из свойства RefreshSQL, который перечитывает значения полей вставленной в таблицу "Goods" записи, основываясь на известном значении ключевого поля "Id".
В этот момент значение поля "GoodsCount" уже изменилось, но это изменение никак не отражается в CategoriesGrid. Мы должны обновить текущую запись в CategoriesDataSet, чтобы увидеть актуальное значение всех полей. Это можно сделать при помощи явного вызова метода CategoriesDataSet.Refresh. Однако этот же метод нам надо будет вызывать и в случае удаления записи из GoodsDataSet. А если бы нам нужно было создать более сложный механизм взаимодействия между master и detail-таблицами, то, скорее всего, метод Refresh нужно было бы вызывать также и после многих других операций.