Назад | Содержание | Вперед |
Наряду с описываемыми в предыдущих главах свойствами и методами, стандартный набор данных Delphi инкапсулирует ряд дополнительных механизмов, облегчающих управление записями и полями.
К ним относятся такие полезные функции, как быстрое перемещение по записям, поиск нужной записи по значениям полей, дополнительная фильтрация записей набора данных без использования возможностей СУБД и т. д. Большинство этих механизмов применяют в своей работе индексы таблиц БД.
Абстрактные методы, обеспечивающие управление данными, реализованы в базовом классе TDataSet (см. гл. 12). А классы-потомки, в свою очередь, реализуют механизмы управления данными в соответствии с возможностями технологий доступа к данным (см. часть IV).
Все рассматриваемые в этой главе методы управления данными в полном объеме доступны только в компонентах, инкапсулирующих таблицу БД. Это связано с тем, что компоненты запросов SQL и хранимых процедур не обеспечивают полноценное использование индексов.
В этой главе рассматриваются следующие вопросы:
В рамках одного проекта таблицы БД можно связывать отношениями "один- ко- многим" и "многие- ко- многим", при этом отношения обязательно устанавливаются между индексированными полями двух таблиц.
При создании отношений в качестве главной таблицы можно использовать любой компонент, инкапсулирующий набор данных. Для задания подчиненной таблицы можно использовать только табличные компоненты (см. гл. 12).
Для установления отношения "один- ко- многим" в наборе данных предназначены два свойства — Mastersource и MasterFieids, которые задаются для подчиненной таблицы. Набор данных главной таблицы не требует никаких дополнительных настроек и заданная связь будет работать только при перемещениях по записям главной таблицы.
Свойство Mastersource определяет компонент TDataSource, который связан с главной таблицей.
Затем при помощи свойства MasterFields необходимо установить отношения между полями главной и подчиненной таблицы. В нем содержится имя индексированного поля, по которому устанавливается связь. Если таких полей несколько, их имена разделяются точкой с запятой. При этом не все поля, входящие в индекс, обязаны участвовать в создании отношения.
Для задания свойства MasterFields можно использовать Редактор связей полей (Field Link Designer), который вызывается щелчком на кнопке в поле редактирования этого свойства в Инспекторе объектов (рис. 14.1).
Рис. 14.1. Редактор связей полей
Здесь в разворачивающемся списке Available Indexes выбирается требуемый индекс для подчиненной таблицы. После этого в списке Detail Fields появляются имена всех полей, входящих в этот индекс. В списке Master Fields отображаются все поля главной таблицы.
Теперь требуется создать связи между полями. Для этого в левом списке выбирается поле подчиненной таблицы, а затем соответствующее ему поле главной таблицы в правом списке. После этого активизируется кнопка Add, щелчок на которой создает отношение по двум полям главной и подчиненной таблиц. Созданная связь отображается в списке Joined Fields.
Примечание
После создания связи по индексированным полям данный индекс становится текущим для набора данных. При этом в зависимости от типа СУБД автоматически заполняется свойство indexName или indexFieldNames.
Уже созданные связи можно удалить. Кнопка Delete удаляет выбранную связь, кнопка Clear — все связи.
После создания связей между полями отношение "один- ко- многим" считается установленным. Теперь достаточно открыть оба набора данных, чтобы увидеть работу отношения.
В качестве примера рассмотрим проект DemoJoins, в котором связываются таблицы из демонстрационной базы данных DBDEMOS. Для этого использованы компоненты ADO, подробнее о которых вы можете узнать из гл. 19.
Таблица Customers представлена в наборе данных компонента CustTable, она содержит данные о покупателях. Таблица Orders представлена в наборе данных компонента ordTable, она содержит данные о заказах. Таблица Employee представлена в наборе данных компонента ЕmpТаblе, она содержат данные о продавцах (табл. 14.2).
Примечание
Приложение DemoJoins не содержит дополнительного исходного кода. Все отношения между таблицами заданы при помощи Инспектора объектов.
Отношение "один- ко- многим" задано между таблицами покупателей (Customers) и заказов (Orders). Таблица покупателей является главной. Для создания отношения установлены следующие значения свойств компонента ordTable (подчиненная таблица).
Свойство MasterSource должно указывать на компонент custsource, связанный с набором данных CustTable.
Свойство MasterFields указывает на поле custNo таблицы Customers.
В наборе данных OrdTable включен вторичный индекс на основе поля CustNo (indexName = 'CustNo').
Рис. 14.2. Главная форма проекта DemoJoins
Таким образом, две таблицы связаны отношением "один- ко- многим" по индексированным полям custNo (номер покупателя). В результате, при перемещении по записям таблицы покупателей, в таблице заказов будут показаны только те заказы, которые относятся к текущему покупателю
Отношение "многие- ко- многим" отличается тем, что подчиненная таблица еще раз связывается в качестве главной с другой подчиненной таблицей аналогичной последовательностью действий, как и в отношении "один- ко- многим".
В приложении DemoJoins отношением "многие- ко- многим" связаны таблицы заказов (Orders) и продавцов (Employee). Таблица заказов уже работает в отношении "один- ко- многим" в качестве подчиненной.
В наборе данных ЕтрТаblе заданы следующие свойства:
В наборе данных реализованы два способа поиска записей по заданным значениям полей. Один способ основан на использовании индексов и является более быстрым, но поиск проводится только по индексированным полям. Второй способ применяет специальные методы классов наборов данных и позволяет проводить поиск по любому сочетанию полей, но он более медленный.
Для организации индексного поиска к набору данных должен быть подключен индекс (свойства IndexName ИЛИ IndexFieldNames).
Метод FindKey проводит поиск записи по заданным в параметре значениям ключевых полей текущего индекса набора данных. В случае успеха курсор набора данных устанавливается на найденной записи, а метод возвращает значение True, в противном случае — False.
Если индекс состоит из нескольких полей, значения для поиска записываются в виде множества, причем отсутствующие значения приравниваются к Null.
Рассмотрим простейший пример, в котором реализован поиск по вторичному индексу в таблице CUSTOLY.DB демонстрационной базы данных DBDEMOS. Индекс основан на полях Last_Name И First_Name (рис. 14.3).
В компоненте таblе1, помимо стандартных настроек на таблицу, при помощи свойства IndexName задан и вторичный индекс (его имя Names). Значения для поиска задаются в компонентах Edit1 и Edit2.
Рис. 14.3. Главная форма проекта DemoFind
Листинг 14.1. Секция Implementation главного модуля Main проекта DemoFind
implementation
{$R *.DFM}
procedure TForml.FormShow(Sender: TObject);
begin
try
Cust.Open;
except
on E: EDBEngineError do ShowMessage('Ошибка при открытии таблицы');
end;
end;
procedure TForml.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Gust.Close;
end;
procedure TForml.FindBtnClick(Sender: TObject);
begin
try
if not Gust.FindKey([Editl.Text, Edit2.Text])
then ShowMessage('Запись не найдена');
except on E: EDatabaseError
do ShowMessage('Ошибка поиска');
end;
end;
end.
Набор данных открывается в методе-обработчике FormShow при открытии формы и закрывается в методе-обработчике Formclose. При щелчке на кнопке FindBtn в метод FindKey передаются значения для поиска из компонентов Edit1 И Edit2.
Индексный поиск можно организовать группой методов, подобно созданию диапазонов. Метод setKey переводит набор данных в состояние dsSetKey, затем должно следовать присваивание ключевым полям значений для поиска. Сам поиск осуществляется методом GotoKey:
with Tablel do begin
SetKey;
Fields[0].Value := '428';
GotoKey; end;
В случае успеха курсор набора данных устанавливается на найденной записи, а метод возвращает значение True. Вместо этого метода можно применять метод GotoNearest, который в случае неудачного поиска ищет запись, минимально отличающуюся от критерия поиска.
Изменение параметров поиска осуществляется методом EditKey.
Для поиска по произвольной выборке полей можно использовать методы Locate и Lookup.
function Locate(const KeyFields: string;
const KeyValues: Variant; Options; TLocateOptions): Boolean;
function Lookup(const KeyFields: string;
const KeyValues: Variant; const ResultFields: string): Variant;
В метод Locate необходимо передать список полей, по которым будет идти поиск (параметр KeyFields, имена полей разделяются точкой с запятой), их требуемые значения (параметр KeyValues, значения разделяются запятой) и настройки поиска (параметр options). В настройках можно задать опцию loCaseinsensitive, которая отключает проверку на регистр символов, и опцию loPartialKey, которая включает поиск с минимальными отличиями. В случае успеха поиска курсор набора данных устанавливается на найденной записи, а метод возвращает значение True.
Tablel.Locate('Last__Name;First_Name', VarArrayOf(['Editl.Text',
'Edit2.Text']), []};
В метод Lookup передается список полей для поиска (параметр KeyFields, имена полей разделяются точкой с запятой) и их требуемые значения (параметр KeyValues, значения разделяются запятой). В случае успешного поиска функция возвращает массив значений типа вариант для полей, названия которых содержатся в параметре ResultFields.
Tablel.Lookup('Last_Name;First_Name',
VarArrayOf(['Editl.Text', 1Edit2.Text']), 'Last_Name;First_Name');
Оба эти метода автоматически используют быстрый индексный поиск в случае, если в параметре KeyFields задать поля индекса.
Наиболее эффективным способом отбора записей в набор данных (особенно из больших таблиц) является создание и выполнение соответствующего запроса SQL. Но что делать, если набор данных функционирует на базе табличного компонента? В этом случае на помощь приходит встроенный в набор данных механизм фильтрации данных.
Применение фильтра основано всего на двух основных свойствах и одном вспомогательном. Текст фильтра должен содержаться в свойстве Filter, a свойство Filtered включает и выключает фильтр. Параметры фильтра определяются свойством FilterOptions.
Примечание
Компонент TQuery также может использовать фильтры. Эта возможность подчас позволяет легко и изящно решать довольно сложные проблемы, которые иначе требуют изменения текста запроса или создания нового компонента запроса.
При использовании фильтра его текст транслируется в синтаксис SQL и передается для выполнения на сервер или через соответствующий драйвер в локальную СУБД.
Фильтры можно создавать двумя способами:
Фильтры можно разделить на статические и динамические.
Статические фильтры создаются во время разработки приложения и могут использовать как свойство Filter, так и метод OnFilterRecord.
Динамические фильтры можно создавать и редактировать во время выполнения приложения, для них используется только свойство Filter.
При создании текста фильтра для свойства Filter используются имена полей соответствующей таблицы БД, а для задания отношений применяются все операторы сравнения (>, >=, <, <=, =, <>) и логические операторы (AND, OR, NOT):
Fieldl>100 AND Field2=20
Сравнивать между собой два поля нельзя. Следующий фильтр вызовет ошибку при попытке использования:
ItemCount=Balance AND InputPrice>OutputPrice
При создании динамических фильтров можно изменять как выражение фильтра целиком, так и его части. Например, ограничивающее значение для поля можно задавать при помощи элементов управления формы, что позволяет пользователю приложения управлять фильтрацией набора данных:
procedure TForml.EditlChange(Sender: TObject);
begin
with Tablel do begin
Filtered := False;
Filter := 'Fieldl>=' + TEdit(Sender).Text; Filtered := True;
end;
end;
В фильтрах можно производить отбор по частям строк для строковых полей, для этого используется символ звездочка:
ItemName='A*'
Фильтр начинает работать только после того, как свойству Filtered присваивается истинное значение. Перед изменением текста динамического фильтра или для отключения фильтра свойству Filtered присваивается значение False.
Параметры фильтра определяются свойством FilterOptions:
property FiiterOptions: TFilterOptions;
TFilterOption = (foCaselnsensitive, foNoPartialCompare);
TFilterOptions = set of TFilterOption;
Параметр foCaselnsensitive, будучи включенным в свойстве, отключает сравнение строковых значений с учетом регистра символов.
Параметр foNoPartialCompare отключает отбор строковых значений по части строки.
Метод-обработчик onFilterRecord имеет следующее объявление:
type TFilterRecordEvent = procedure(DataSet: TDataSet; var Accept:
Boolean) of object;
property OnFiiterRecord: TFilterRecordEvent;
Если этот метод создан для набора данных, то он вызывается для каждой его записи. Программный код метода должен присваивать параметру Accept истинное или ложное значение. В результате запись передается в набор данных или отсекается:
procedure TForml.TablelFilterRecord(DataSet: TDataSet;
var Accept: Boolean);
begin
Accept := ArchOrdersArchDat.AsString >= DateEditl.Text;
end;
Важнейшее преимущество метода onFilterRecord, по сравнению со свойством Filter, заключается в том, что в этом методе-обработчике можно сравнивать поля и производить вычисления над их значениями.
Недостатком метода является недостаточная гибкость, хотя такой фильтр можно модифицировать путем присвоения методу процедурной переменной, содержащей ссылку на новый метод.
Закладки, как инструмент работы с записями набора данных, позволяют осуществлять быстрое перемещение на нужную запись. Набор данных может содержать неограниченное число закладок, каждая из которых представляет собой указатель. Закладку можно создать только для текущей записи набора данных.
При работе с закладками используются три основных метода:
Кроме этого, можно использовать метод Bookmarkvalid, который проверяет, указывает ли закладка на реально существующую запись. Метод compareBookmark позволяет сравнить между собой две закладки:
var Bookmarkl, Bookmark2: TBookmark;
...
if Tablel.CompareBookmark(Bookmarkl, Bookmark2) = 1
then ShowMessage (' Закладки одинаковы') ;
В наборе данных имеется свойство Bookmark, которое содержит название текущей закладки.
Рассмотрим небольшой пример, где право управлять закладками предоставлено пользователю (рис. 14.4). На форме, помимо других элементов управления (среди которых есть компонент TDBGrid), имеются две кнопки. Кнопка startBookmark помечает текущую запись, кнопка stopBookmark переходит к закладке, а затем уничтожает ее.
Рис. 14.4. Главная форма проекта DemoBookmark
Листинг 14.2. Пример использования закладок .
implementation
{$R *.DFM}
var SaveRecPos: TBookMark;
procedure TMainForm.FormShow(Sender: TObject);
begin
try
Cust.Open;
BookmarkControl.Brush.Color := clBtnFace;
except
ShowMessage('Ошибка открытия набора данных');
end;
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Cust.Close;
end;
procedure TMainForm.StartBookmarkClick(Sender: TObject);
begin
if Not Cust.BookmarkValid(SaveRecPos)
then SaveRecPos := Cust.GetBookmark;
BookmarkControl.Brush.Color := cILime
end;
procedure TMainForm.StopBookmarkClick(Sender: TObject);
begin
with Cust do begin if Not BookmarkValid(SaveRecPos)
then Exit;
GotoBookmark(SaveRecPos);
FreeBookmark(SaveRecPos);
SaveRecPos := Nil;
end;
BookmarkControl.Brush.Color := clBtnFace;
end;
end.
Использование метода Bookmarkvaiid позволяет корректно переопределять закладку, если она уже установлена, и избежать ошибок при произвольных нажатиях кнопок. Компонент BookmarkControl типа TShape сигнализирует о том, что закладка установлена или удалена.
Примечание
Закладки также используются в компоненте TDBGrid. Он имеет свойство SelectedRows типа TBookmarkList, которое представляет собой список закладок, указывающих на одновременно выделенные записи.
В наборе данных, помимо фильтров, имеется еще одно средство отбора записей. Группа методов позволяет на основе использования индексов отбирать в набор данных только те записи, значения индексированных полей которых (для текущего индекса) соответствуют диапазону заданных величин.
Диапазоны работают быстрее фильтров, но менее гибки и не так удобны в работе. При использовании диапазонов набор данных обязательно должен находиться в состоянии dsSetKey (см. ниже).
Для того чтобы включить диапазон, необходимо задать стартовое и конечное значение диапазона для ключевых полей, затем применить созданный диапазон к набору данных. Работающий диапазон можно модифицировать.
Примечание
Все методы работы с диапазонами используют те поля, которые заданы в текущем индексе. Для таблиц Paradox и dBASE это свойство indexName. Для таблиц серверов SQL это свойство indexFieldNames.
Метод setRangestart переводит набор данных в режим dsSetKey, следующее за этим присваивание ключевым полям значений означает задание начальной границы диапазона.
Метод setRangeEnd переводит набор данных в режим dsSetKey, следующее за этим присваивание ключевым полям значений означает задание конечной границы диапазона.
После этого необходимо использовать метод AppiyRange, который применяет созданный диапазон к набору данных:
with Tablel do
begin
SetRangeStart;
Fields[0].Value := '439';
SetRangeEnd;
Fields[1].Value := '522';
AppiyRange;
end;
Работающий диапазон можно модифицировать аналогичным образом: после вызова методов EditRangestart и EditRangeEnd необходимо задать новые границы для ключевых полей и снова вызвать метод ApplyRange:
with Tablel do
begin
EditRangeStart;
Fields[0].Value := '500';
EditRangeEnd;
Fields[1].Value := '522';
AppiyRange;
end;
Отмена диапазона осуществляется методом CancelRange.
Если индекс содержит несколько полей, то перед вызовом метода AppiyRange необходимо задать значения для всех ключевых полей.
Для одновременного задания верхней и нижней границы диапазона можно использовать метод SetRange.
with Tabiel do
begin
SetRange(['500'], ['522']);
AppiyRange;
end;
Тем, какая граница будет у диапазона — открытая или закрытая, управляет свойство KeyExclusive. Если оно имеет значение True, граничные значения в диапазон не включаются, в противном случае — включаются.
Разработчик приложений БД в Delphi может использовать ряд полезных механизмов набора данных, которые реализованы для компонентов всех технологий доступа к данным.
К этим механизмам относятся методы быстрого поиска и перехода к найденным записям; связывания наборов данных по индексированным полям; метод дополнительной фильтрации записей набора данных.
Назад | Содержание | Вперед |