Абстрактный набор данных
В основе иерархии классов, обеспечивающих функционирование наборов данных в приложениях баз данных Delphi, лежит класс TDataSet. Хотя он почти не содержит методов, реально обеспечивающих работоспособность основных механизмов набора данных, тем не менее его значение трудно переоценить.
Этот класс задает структурную основу функционирования набора данных. Другими словами, это скелет набора данных, к методам которого необходимо лишь добавить требуемые вызовы соответствующих функций реальных технологий.
При решении наиболее распространенных задач программирования в процессе создания приложений баз данных класс TDataSet не нужен. Тем не менее знание основных принципов работы набора данных всегда полезно. Кроме этого, класс TDataSet может использоваться разработчиками в качестве основы для создания собственных компонентов. Поэтому рассмотрим основные механизмы, реализованные в наборе данных.
Набор данных открывается и закрывается свойством
property Active: Boolean;
которому соответственно необходимо присвоить значение True или False. Аналогичные действия выполняют методы
procedure Open;
procedure Close;
После открытия набора данных можно перемещаться по его записям.
На одну запись вперед и назад перемещают курсор соответственно методы
procedure Next;
procedure Prior;
На первую и последнюю запись можно попасть, используя соответственно методы
procedure First;
procedure Last;
Признаком того, что достигнута последняя запись набора, является свойство
property Eof: Boolean;
которое в этом случае имеет значение True.
Аналогичную функцию для первой записи выполняет свойство
property Bof: Boolean;
Перемещение вперед и назад на заданное число записей выполняет метод
function MoveBy(Distance: Integer): Integer;
Параметр Distance определяет число записей. Если параметр отрицательный — перемещение осуществляется к началу набора данных, иначе — к концу.
Для ускоренного перемещения по набору данных можно отключить все связанные компоненты отображения данных. Это делается методом
procedure DisableControls;
Обратная операция выполняется методом
procedure EnableControls;
Общее число записей набора данных возвращает свойство
property RecordCount: Integer;
однако использовать его нужно аккуратно, т. к. каждое обращение к этому свойству приводит к обновлению набора данных, что может вызвать проблемы для больших таблиц или сложных запросов. Если вам нужно определить, не является ли набор данных пустым (часто используемая операция), можно использовать метод
function IsEmpty: Boolean;
который возвращает значение True, если набор данных пуст, или уже упоминавшиеся свойства
...
if MyTable.Bof and MyTable.Eof
then ShowMessage('DataSet is empty');
...
Номер текущей записи позволяет узнать свойство
property RecNo: Integer;
Размер записи в байтах возвращает свойство
property RecordSize: Word;
Каждая запись набора данных представляет собой совокупность значений полей таблицы. В зависимости от типа компонента и его настройки, число полей в наборе данных может изменяться. И совсем не обязательно набор данных должен содержать все поля таблицы базы данных.
Совокупность полей набора данных инкапсулирует свойство
property Fields: TFields;
а все необходимые параметры полей содержатся в свойстве
property FieldDefs: TFieldDefs;
Общее число полей набора данных возвращает свойство
property FieldCount: Integer;
а общее число полей типа BLOB содержится в свойстве
property BlobFieldCount: Integer;
Доступ к значениям полей текущей записи предоставляет свойство
property FieldValues[const FieldName: string]: Variant; default;
где в параметре FieldName задается имя поля.
В процессе программирования разработчик очень часто обращается к полям набора данных. Если структура полей набора данных жестко задана и не изменяется, это можно сделать так:
for i := 0 to MyTable.FieldCount - 1 do
MyTable.Fields[i].DiplayFormat := '#.###';
Иначе, если порядок следования полей и их состав меняется, можно использовать метод
function FieldByName(const FieldName: string): TField;
И делается это следующим образом:
MyTable.FieldByName('VENDORNO').Aslnteger := 1234;
Имя поля, передаваемое в параметре FieldName, не чувствительно к регистру символов.
Метод
procedure GetFieldNames(List: TStrings);
вернет в параметр List полный список имен полей набора данных.
Более подробная информация о полях и способах работы с ними содержится в гл. 13.
Класс TDataSet содержит ряд свойств и методов, которые обеспечивают редактирование набора данных.
Но сначала бывает полезно поинтересоваться, можно ли редактировать набор данных вообще. Это можно сделать при помощи свойства
property CanModify: Boolean;
которое принимает значение True для редактируемых наборов. Перед началом редактирования набор данных нужно перевести в режим редактирования, использовав метод
procedure Edit;
Для сохранения сделанных изменений применяется метод
procedure Post; virtual;
Разработчик может вызывать его самостоятельно, или же метод Post вызывается самим набором данных при переходе на другую запись.
При необходимости все сделанные после последнего вызова метода Post изменения можно отменить методом
procedure Cancel; virtual;
Новая пустая запись добавляется в конец набора данных методом
procedure Append;
Новая пустая запись добавляется на место текущей методом
procedure Insert;
а текущая запись и все нижеследующие смещаются на одну позицию вниз.
Внимание
При использовании методов Append и insert набор данных переходит в режим редактирования самостоятельно.
Дополнительно, у вас есть возможность добавить или вставить новую запись уже с заполненными полями. Для этого применяются методы
procedure AppendRecord(const Values: array of const); procedure InsertRecord(const Values: array of const);
А делается это примерно так:
МуТаblе.AppendRecord([2345, 'New customer', '+7(812)4569012', 0, '']);
После вызова этих методов и их завершения набор данных автоматически возвращается в состояние просмотра.
Для существующей записи аналогичным образом можно заполнить все поля, использовав метод
procedure SetFields(const Values: array of const);
Текущая запись удаляется методом
procedure Delete;
При этом набор данных не выдает никаких предупреждений, а просто делает это.
Очистить содержимое всех полей текущей записи может метод
procedure ClearFields;
Обратите внимание, что поля становятся пустыми (NULL), а не сбрасываются в нулевое значение.
О том, редактировалась ли текущая запись, сообщает свойство
property Modified: Boolean;
если оно имеет значение True.
Набор данных можно обновить, не закрывая и не открывая его снова. Для этого применяется метод
procedure Refresh;
Однако он сработает только для таблиц и тех запросов, которые нельзя редактировать.
В каждый момент времени набор данных находится в определенном состоянии (о состояниях см. ниже в этой главе). Свойство
type TDataSetState = (dslnactive, dsBrowse, dsEdit, dslnsert, dsSetKey, dsCalcFields, dsFilter, dsNewValue, dsOldValue, dsCurValue, dsBlockRead, dsInternalCalc, dsOpening); property State: TDataSetState;
дает информацию о текущем состоянии набора.
Методы-обработчики класса TDataSet предоставляют разработчику широчайшие возможности по отслеживанию событий, происходящих с набором данных.
По паре методов-обработчиков (до и после события) предусмотрено для следующих событий в наборе данных:
открытие и закрытие набора данных; переход в режим редактирования; переход в режим вставки новой записи; сохранение сделанных изменений; отмена сделанных изменений; перемещение по записям набора данных; обновление набора данных. Обратите внимание, что помимо методов-обработчиков режима вставки существует дополнительный метод
property OnNewRecord: TDataSetNotifyEvent;
который вызывается непосредственно при вставке или добавлении записи. Дополнительно к этому могут использоваться методы-обработчики возникающих ошибок. Они предусмотрены для ошибок удаления, редактирования и сохранения изменений.
Метод-обработчик
property OnCalcFields: TDataSetNotifyEvent;
очень важен для задания значений вычисляемых полей. Он вызывается для каждой записи, которая отображается в визуальных компонентах, связанных с набором данных каждый раз, когда необходимо перерисовать значения полей в визуальных компонентах.
Если в методе-обработчике OnCalcFields производятся слишком сложные вычисления, частота его вызовов может быть уменьшена за счет свойства
property AutoCalcFields: Boolean;
По умолчанию оно равно значению True и расчет вычисляемых полей производится при каждой перерисовке. При значении False метод-обработчик OnCalcFields вызывается только при открытии, переходе в состояние редактирования и обновлении набора данных. Все перечисленные выше обработчики имеют одинаковый тип
type TDataSetNotifyEvent = procedure(DataSet: TDataSet) of object;
И метод-обработчик
type TFilterRecordEvent = procedure(DataSet: TDataSet;
var Accept: Boolean) of object;
property OnFilterRecord: TFilterRecordEvent;
вызывается для каждой записи набора данных при свойстве Filtered = True. (Подробнее об этих свойствах и методе-обработчике см. гл. 14.) Помимо перечисленных, класс TDataSet содержит еще много свойств и методов, которые обеспечивают работоспособность многих полезных в практическом программировании приложений баз данных функций. Подробно они рассмотрены в гл. 14.
Индексы в наборе данных
Важнейшей проблемой для любой БД является достижение максимальной производительности и ее сохранение при дальнейшем увеличении объемов хранимых данных. Использование индексов позволяет решить эту задачу.
Индекс представляет собой часть базы данных, в которой содержится информация об организации данных в таблицах БД.
В отличие от ключей, которые просто идентифицируют отдельные записи, индексы занимают дополнительные объемы памяти (довольно значительные) и могут храниться как совместно с таблицами, так и в виде отдельных файлов. Индексы создаются вместе со своей таблицей и обновляются при модификации данных. При этом работа по обновлению индекса для большой таблицы может отнимать много ресурсов, поэтому имеет смысл ограничить число индексов для таких таблиц, где происходит частое обновление данных.
Индекс содержит в себе уникальные идентификаторы записей и дополнительную информацию об организации данных. Поэтому если при выполнении запроса сервер или локальная СУБД обращается для отбора записи к индексу, то это занимает значительно меньше времени, т. к. понятно, что идентификатор гораздо меньше самой записи. Кроме этого, индекс "знает", как организованы данные и может ускорять обработку за счет группирования записей по сходным значениям параметров.
Создание для БД эффективного набора индексов является нетривиальной задачей.
Во-первых, нужно верно определить оптимальное число индексов для каждой таблицы. Во-вторых, каждый индекс должен содержать только необходимые поля, при этом большую роль играет их упорядочивание.
В большинстве СУБД при создании индексов требуется только задать поля и название индекса, вся остальная работа выполняется автоматически.
Естественно, что в компонентах доступа к данным VCL Delphi используются все возможности такого мощного инструмента, как индексы. Причем свойства и методы для работы с индексами присутствуют только в табличных компонентах, т. к. в компонентах запросов работа с индексами осуществляется средствами SQL.
Набор данных может работать и без применения индексов, но для этого соответствующая таблица БД не должна иметь первичного ключа — случай довольно редкий. Поэтому по умолчанию в наборе данных используется первичный индекс. При открытии набора данных все записи отсортированы в соответствии с первичным ключом.
Использование описаний индексов
Описания индексов наряду с описаниями полей (см. г/г. 13) также используются при создании новых таблиц БД. Для каждого планируемого индекса перед вызовом метода CreateTable необходимо создать или скопировать из существующего набора данных соответствующее описание. Тогда при создании таблицы индексы будут добавлены автоматически:
with Tablel do
begin
DatabaseName := 'DBDEMOS';
TableType := ttParadox;
TableName := 'DemoTable';
...
{Создание описаний полей}
...
with IndexDefs do begin Clear;
AddlndexDef; with Items[0] do
begin
Name := ' ' ; Fields := 'Fieldl'; Options := [ixPrimary, ixUnique];
end;
AddlndexDef; with Items[1] do
begin
Name := 'Secondlndex'; Fields := 'Fieldl;Field2';
Options := [ixCaselnsensitive];
end;
end;
CreateTable;
end;
При создании описаний индексов использован метод AddlndexDef, который при каждом вызове добавляет к списку Items объекта TIndexDefs новый объект TindexDef. Таким образом сначала создается первичный индекс (в таблицах Paradox он не имеет имени), затем вторичный индекс SecondIndex. Для каждого описания обязательно определяются составляющие индекс поля и параметры индекса (свойства Fields и options).
Класс TParam
Класс TParam инкапсулирует свойства отдельного параметра. Имя параметра определяется свойством
property Name: String;
Тип данных параметра задает свойство
property DataType: TFieldType;
Тип данных параметра и связанного поля должны совпадать.
Тип параметра определяется множеством
type
TParamType = (ptUnknown, ptInput, ptOutput, ptlnputOutput, ptResult); TParamTypes = set of TParamType;
которое имеет следующие значения:
ptUnknown — тип неизвестен; ptinput — параметр предназначен для передачи значения из приложения; ptOutput — параметр предназначен для передачи значения в приложение; ptlnputOutput — параметр предназначен для передачи и приема значения; ptResult — параметр предназначен для передачи в приложения информации о статусе операции.Свойство
property ParamType: TParamType;
определяет тип параметра.
При работе с параметрами довольно часто бывает необходимо определить, имеет ли параметр ненулевое значение. Для этого используется свойство
property IsNull: Boolean;
Свойство возвращает значение True, если параметр не имеет значения или имеет значение Null.
Свойство
property Bound: Boolean;
возвращает значение True только тогда, когда параметру не присваивалось значение вообще.
Метод
procedure Clear;
присваивает параметру значение Null.
Само значение параметра задается свойством
property Value: Variant;
Но использование вариантов не очень эффективно, когда требуется обеспечить максимальную скорость. В таких случаях можно обратиться к целому набору свойств AS ..., которые не только возвращают значение, но и приводят его к некоторому типу. Например, свойство
property Aslnteger: Longlnt;
возвращает целочисленное значение поля.
Примечание
Необходимо осторожно использовать свойства с приведением типа, т. к. попытка преобразования неверного значения вызовет исключительную ситуацию.
Для чтения из буфера и записи в буфер значения параметра соответственно используются методы
procedure SetData(Buffer: Pointer);
procedure GetData(Buffer: Pointer);
а необходимый размер при записи в буфер позволит определить метод
function GetDataSize: Integer;
Можно скопировать тип данных, имя и значение параметра прямо из поля данных. Для этого применяется метод
procedure AssignField(Field: TField);
а для присвоения типа данных и значения используется метод
procedure AssignFieldValue(Field: TField; const Value: Variant);
Общее число знаков для числовых значений определяет свойство
property Precision: Integer;
А свойство
property NumericScale: Integer;
задает число знаков после запятой.
Для строковых параметров размер задает свойство
property Size: Integer;
Класс TParams
Класс TParams представляет собой список параметров.
Доступ к элементам списка возможен через индексированное свойство
property Items[Index: Word]: TParam;
а к значениям параметров — через свойство
property ParamValues[const ParamName: String]: Variant;
Добавить новый параметр можно методом
procedure AddParam(Value: TParam);
Но для него необходимо создать объект параметра. Это можно сделать методом
function CreateParamfFldType: TFieldType; const ParamName: string; ParamType: TParamType): TParam;
где FidType — тип данных параметра, ParamName — имя параметра и ParamType — тип параметра (см. ниже).
И оба метода можно использовать в связке:
MyParams.AddParam(MyParams.CreateParam(ftInteger, 'Paraml', ptInput));
Вместо того, чтобы заполнять параметры по одному, можно использовать метод
function ParseSQL(SQL: String; DoCreate: Boolean): String;
который при DoCreate = True анализирует текст запроса из свойства SQL и создает новый список параметров.
Или же, для присвоения значений сразу всем параметрам используется метод
procedure AssignValues(Value: TParams);
Для удаления параметра из списка применяется метод
procedure RemoveParam(Value: TParam);
При работе с параметрами для их идентификации полезно использовать обращение по имени, т. к. при работе с хранимыми процедурами после их выполнения порядок следования может измениться. Также и при использовании динамических запросов (их текст SQL может изменяться во время выполнения).
Для обращения к параметру по имени используется метод
function ParamByName(const Value: String): TParam;
В сложных запросах SQL или после многочисленных исправлений разработчик может допустить ошибку и создать два разных параметра с одним именем. В этом случае при выполнении запроса одноименные параметры считаются одним и им присваиваются значение первого по порядку запроса. Для контроля повторных имен в списке параметра используется метод
function IsEqual(Value: TParams): Boolean;
который возвращает значение True, если для параметра value найден дубликат.
Компонент хранимой процедуры
Компонент хранимой процедуры предназначен для определения процедуры, установки ее параметров, выполнения процедуры и возвращения результатов в компонент.
В зависимости от выбранной технологии доступа к данным, каждый компонент хранимой процедуры имеет собственный способ соединения с сервером. После подключения к источнику данных имя хранимой процедуры можно выбрать из списка свойства
property StoredProcName: String;
После этого свойство
property Params: TParams;
предназначенное для хранения параметров процедуры, автоматически заполняется.
Для хранимых процедур важно деление параметров на входные и выходные. Первые содержат исходные данные, а вторые передают результаты выполнения процедуры.
Детально класс TParams описывается ниже. Общее число параметров возвращает свойство
property ParamCount: Word;
Для подготовки хранимой процедуры используется метод
procedure Prepare;
или свойство
property Prepared: Boolean;
которое должно получить значение True.
Метод
procedure UnPrepare;
или свойство Prepared := False выполняют обратное действие.
Кроме того, проверка значения свойства Prepared позволяет установить, осуществлялась ли подготовка процедуры к выполнению или нет.
Внимание
После выполнения хранимой процедуры исходный порядок следования параметров в списке Params может измениться. Поэтому для доступа к конкретному параметру рекомендуется использовать метод
function ParamByName(const Value: String): TParam;
Если хранимая процедура возвращает набор данных, компонент можно открывать методом
procedure Open;
или свойством
property Active: Boolean;
В противном случае для выполнения процедуры используется метод
procedure ExecProc;
и после этого выходные параметры получат вычисленные значения.
Компонент таблицы
Компонент таблицы обеспечивает доступ к таблице базы данных целиком, создавая набор данных, структура полей которого полностью повторяет таблицу БД. За счет этого компонент прост в настройке и обладает многими дополнительными функциями, которые обеспечивают применение табличных индексов.
Но в практике программирования работа с таблицами целиком используется не так часто. А при работе с серверами баз данных промежуточное ПО, используемых технологий доступа к данным, все равно транслирует запрос на получение табличного набора данных в простейший запрос SQL, например:
SELECT * FROM Orders
В такой ситуации применение табличных компонентов становится менее эффективным, чем использование запросов.
После соединения с источником данных (процесс подключения для каждой технологии подробно рассматривается в части IV) необходимо задать имя таблицы в свойстве
property TableName: String;
Иногда в свойстве TаblеТуре дополнительно задается тип таблицы.
Если соединение с источником данных настроено правильно, имя таблицы можно выбрать из выпадающего списка свойства TableName.
Преимуществом табличного компонента является использование индексов, которые ускоряют работу с таблицей. Все индексы, созданные в базе данных для таблицы, автоматически загружаются в компонент. Их параметры доступны через свойство
property IndexDefs: TIndexDefs;
Подробно класс TIndexDefs рассматривается ниже в этой главе.
При работе с компонентом разработчик имеет возможность управлять индексами.
Существующий индекс можно выбрать в Инспекторе объектов в списке свойств
property IndexName: String;
или использовать свойство
property IndexFieldNames: String;
в котором можно задать произвольное сочетание имен индексированных полей таблицы. Имена полей разделяются символом точкой с запятой. Таким образом, при помощи свойства IndexFieldNames можно создавать составные индексы.
Свойства IndexName и IndexFieldNames нельзя использовать одновременно.
Число полей, используемых в текущем индексе табличного компонента, возвращает свойство
property IndexFieldCcunt: Integer;
А свойство
property IndexFields: [Index: Integer]: TField;
представляет собой индексированный список полей, входящих в текущий индекс:
for i := 0 to MyTable.IndexFieldCount — 1 do MyTable.IndexFields[i].Enabled := False;
Для выполнения операций с таблицами и индексами целиком в табличных компонентах реализовано несколько методов.
Метод
procedure CreateTable;
создает новую таблицу в базе данных, используя заданное имя и описание полей, и индексов из свойств TFieldDefs и TindexDefs. Если таблица с таким именем уже имеется в базе данных, то она будет уничтожена и создана заново с новой структурой и данными.
Метод
procedure EmptyTable;
удаляет из набора данных и таблицы базы данных все записи.
Метод
procedure DeleteTable;
уничтожает таблицу базы данных, связанную с компонентом. Набор данных должен быть закрыт.
Метод
type
TIndexOption = (ixPrimary, ixUnique, ixDescending, ixCaselnsensitive,
ixExpression, ixNonMaintained);
TIndexOptions = set of TIndexOption;
procedure Addlndex(const Name, Fields: String; Options: TIndexOptions, const DescFields: String='');
добавляет к таблице БД новый индекс. Параметр Name задает имя индекса. В параметре Fields через точку с запятой определяются имена полей, входящих в индекс. Параметр DescFields задает описание индекса из констант, объявленных в типе TIndexOption.
Метод
procedure Deletelndex(const Name: string);
уничтожает индекс.
Кроме этого, табличные компоненты содержат свойства и методы, описываемые в гл. 14.
Компонент запроса
Компонент запроса предназначен для создания запроса SQL, подготовки его параметров, передачи запроса на сервер БД и представления результата запроса в наборе данных. При этом набор данных может быть редактируемым или нет.
Любой компонент запроса, каждая строка набора данных которого однозначно связывается с одной строкой таблицы БД, может редактироваться. Например, запрос
SELECT * FROM Country
редактировать можно. Если же приведенное правило не выполняется, то набор данных можно использовать только для просмотра, и, конечно, возможности компонентов здесь ни при чем. Куда, к примеру, записывать результаты редактирования записей следующего запроса:
SELECT CustNo, SUM(AmountPaid)
FROM Orders
GROUP BY CustNo
Ведь в таком запросе каждая запись есть результат суммирования неизвестного заранее числа других записей.
Тем не менее компоненты запросов предоставляют разработчику мощный и гибкий механизм работы с данными. С помощью компонентов запросов можно решать гораздо более сложные задачи, чем с табличными компонентами.
В целом компонент запроса работает быстрее, т. к. структура возвращаемых запросом полей может изменяться, то экземпляры класса TFieldDef, хранящие информацию о свойствах полей, создаются по необходимости при запуске приложения. Табличный компонент создает все классы для описания полей в любом случае, поэтому в приложениях клиент-сервер табличный компонент может открываться медленнее, чем запрос. Рассмотрим общие свойства и методы компонентов запросов. Текст запроса определяется свойством
property SQL: TStrings;
В свойстве
property Text: PChar;
содержится окончательно подготовленный текст запроса перед пересылкой его на сервер.
Выполнение запроса возможно тремя способами.
Если запрос возвращает результат в набор данных, то применяется метод
procedure Open;
или свойство
property Active: Boolean;
которому присваивается значение True. После выполнения запроса открывается набор данных компонента. Закрывается такой запрос методом
procedure Close;
или тем же свойством Active.
Если запрос не возвращает результат в набор данных (например, использует операторы INSERT, DELETE, UPDATE), то используется метод
procedure ExecSQL;
и после выполнения запроса набор данных компонента не открывается.
Попытка использовать для такого запроса метод open или свойство Active приведет к ошибке создания указателя на курсор данных.
После выполнения запроса в свойстве
property RowsAffected: Integer;
возвращается число обработанных при выполнении запроса записей.
Для того чтобы разрешить редактирование набора данных запроса, необходимо свойству
property RequestLive: Boolean;
присвоить значение True. Это свойство устанавливается, но не работает для запроса, результат которого не модифицируется из-за самого запроса.
Для подготовки запроса к выполнению предназначен метод
procedure Prepare;
который обеспечивает выделение необходимых ресурсов на сервере и проведение оптимизации.
Метод
procedure UnPrepare;
освобождает занятые при подготовке запроса ресурсы.
Результат выполнения этих двух операций отражается в свойстве
property Prepared: Boolean;
Значение True данного свойства говорит о том, что запрос подготовлен для выполнения.
Вызов методов Prepare и UPРrераrе не является обязательным, т. к. компонент делает это автоматически. Однако если запрос будет выполняться несколько раз подряд, то подготовку необходимо провести перед первым выполнением запроса вручную. Тогда при последующих выполнениях сервер не будет тратить время на проведение бесполезной операции — ведь ресурсы под запрос уже были выделены.
Часто запросы имеют настраиваемые параметры, значения которых определяются непосредственно перед выполнением запроса.
Свойство
property Params: TParams;
представляет собой список объектов TParams, каждый из которых содержит настройки одного параметра. Свойство Params обновляется автоматически при изменении текста запроса. Подробнее о классе TParams рассказывается ниже в этой главе.
Примечание
В компоненте TADOQuery свойство, аналогичное описанному свойству Params, называется Parameters.
Свойство
property ParamCount: Word;
возвращает число параметров запроса.
Свойство
property ParamCheck: Boolean;
определяет, необходимо ли обновлять свойство Params при изменении текста запроса во время выполнения. При значении True обновление осуществляется.
Кроме этого, компоненты запросов содержат некоторые свойства и методы, описываемые в гл. 14.
Механизм подключения индексов
Для того чтобы подключить к набору данных вторичный индекс, необходимо присвоить свойству indexName название индекса. Если свойство не имеет значения, то в наборе данных используется первичный индекс.
Альтернативный способ задания индекса заключается в использовании свойства indexFieldNames, в котором задается перечень имен полей необходимого индекса, разделенных точкой с запятой. При этом в Инспекторе объектов для данного свойства список полей существующих индексов создается автоматически, разработчику остается сделать выбор. При помощи свойства indexFieldNames можно создавать и составные индексы. Для этого необходимо, чтобы все входящие в список поля были индексированы.
Список имен всех индексов можно получить при помощи метода
GetlndexNames.
Примечание
Изменение текущего индекса можно осуществлять без отключения набора данных, поэтому в приложениях очень удобно делать сортировку данных по индексам. Такой метод смены индексов называется индексацией "на лету".
После установки индекса количество полей в индексе передается в свойство
IndexFieldCount.
Набор данных
Любое приложение баз данных должно уметь выполнять как минимум две операции. Во-первых, иметь информацию о местонахождении базы данных, подключаться к ней и считывать имеющуюся в таблицах БД информацию. Эта функция в значительной степени зависит от реализации конкретной технологии доступа к данным.
Во-вторых, обеспечивать представление и редактирование полученных данных. Множество записей одной или нескольких таблиц, переданные в приложение в результате активизации компонента доступа к данным, будем называть набором данных. Понятно, что в объектно-ориентированной среде для представления какой-либо группы записей приложение должно использовать возможности некоторого класса. Этот класс должен инкапсулировать набор данных и обладать методами для управления записями и полями.
Таким образом, сам набор данных и класс набора данных является той осью, вокруг которой вращается любая деятельность приложения баз данных.
Пользователь просматривает на экране данные — это результат использования набора данных.
Пользователь решил изменить какое-то число — он изменит содержимое ячейки набора данных.
При закрытии приложение сохраняет все изменения — это набор данных передается в базу данных для сохранения.
При этом, используя одни базовые функции для обслуживания набора данных, компоненты должны обеспечивать доступ к данным в рамках различных технологий. Поэтому не удивительно, что разработчики VCL уделили особое внимание созданию максимально эффективной иерархии классов, обеспечивающих использование наборов данных (рис. 12.1).
Класс TDataset является базовым классом иерархии, он инкапсулирует абстрактный набор данных и реализует максимально общие методы работы с ним. В него можно передать записи из таблицы базы данных или строки из обычного текстового файла — набор данных будет функционировать одинаково хорошо.
![](image/gl-12-1.gif)
Рис. 12.1. Иерархия классов, обеспечивающих функционирование набора данных
На основе базового класса реализованы специальные компоненты VCL для различных технологий доступа к данным, которые позволяют разработчику конструировать приложения баз данных, используя одни и те же приемы и настраивая одинаковые свойства.
В этой главе рассматриваются следующие вопросы:
набор данных, инкапсулированный в классе TDataSet; что такое состояния набора данных; индексы, поля, параметры; прототипы компонентов для работы с таблицами, запросами и хранимыми процедурами; основные механизмы набора данных, реализованные в классе TDataSet.
Описание индекса
Параметры каждого индекса набора данных представлены в классе TindexDef, а их совокупность для набора данных содержится в свойстве IndexDefs класса TDataSet.
Свойство
property Name: String;
определяет название индекса.
Список всех полей индекса содержится в свойстве
property Fields: String;
Поля разделяются точкой с запятой.
Свойство
property CaselnsFields: String;
содержит список полей, регистр символов в которых при сортировке не учитывается. Поля разделяются точкой с запятой. Все поля из этого списка должны входить в свойство Fields. В наборе данных по умолчанию используется сортировка записей с учетом регистра символов. Но некоторые серверы БД допускают комбинированную сортировку по полям с учетом регистра и без.
Свойство
property DescFields: String;
содержит список полей через точку с запятой, которые сортируются в обратном порядке. Все поля из этого списка должны входить в свойство Fields. По умолчанию все поля сортируются в прямом порядке. Некоторые серверы БД поддерживают одновременную сортировку полей в прямом и обратном порядке.
Свойство
property GroupingLevel: Integer;
позволяет ограничить область применения индекса. Если значение этого свойства равно нулю, индекс упорядочивает все записи набора данных. В противном случае действие индекса распространяется на группы записей, имеющих одинаковые значения для того числа полей, которое задано этим свойством.
Параметры индекса определяются свойством
property Options: TIndexOptions;
Для индекса возможны сочетания следующих параметров:
ixPrimary — первичный индекс; ixunique — значения индекса уникальны; ixDescending — индекс сортирует записи в обратном порядке; ixCaseinsensitive — индекс сортирует записи без учета регистра символов; ixExpression — в индексе используется выражение (для индексов dBASE); ixNonMaintained — индекс не обновляется при открытии таблицы.Метод
procedure Assign(ASource: TPersistent); override;
заполняет свойства объекта значениями аналогичных свойств объекта ASource.
Параметры запросов и хранимых процедур
Свойство Params представляет собой набор изменяемых параметров запроса или хранимой процедуры, а также набор объектов TParam, инкапсулирующих отдельные параметры.
Рассмотрим следующий запрос SQL:
SELECT SaleDat, OrderNo
FROM Orders
WHERE SaleDat >= '01.08.2001' AND SaleDat <= '31.08.2001'
В нем осуществляется отбор номеров заказов, сделанных в августе 2001 года. Теперь вполне естественно было бы предположить, что пользователю может понадобиться получить подобный отчет за другой месяц или за первые десять дней августа.
В этом случае можно поступить так:
procedure TForml.FormCreate(Sender: TObject);
begin
with Queryl do
begin
SQL[0] := 'SELECT PartDat, ItemNo, ItemCount, InputPrice';
SQL[1] := 'FROM Parts';
SQL[2] := 'WHERE PartDat>= "01.08.2001" AND PartDat<=" 31. 08 . 2001 ''';
end;
end;
procedure TForml.RunBtnClick(Sender: TObject);
begin
with Queryl do
begin
if Active then Close;
SQL[2] := 'WHERE PartDat>= '+chr(39)+DatelEdit.Text+chr(39)+
AND PartDat<='+chr(39)+Date2Edit.Text+chr(39);
Open;
end;
end;
При создании формы в методе FormCreate задается текст запроса. Для этого используется свойство SQL. При щелчке на кнопке RunBtn, в соответствии с заданными в однострочных редакторах DatelEdit и Date2Edit датах, изменяется текст запроса. Метод FormCreate приведен только для того, чтобы обозначить первоначальный текст запроса, этот текст вполне можно задать в свойстве SQL.
Для решения подобных задач как раз и используются параметры. В этом случае текст запроса будет выглядеть следующим образом:
SELECT PartDat, ItemNo, ItemCount, InputPrice
FROM Parts
WHERE PartDat>= :PD1 AND PartDat<= :PD2
Двоеточие перед именами PD1 и PD2 означает, что это параметры. Имя параметра выбирается произвольно. В списке свойства Params первым идет тот параметр, который расположен первым по тексту запросу.
После ввода в свойстве SQL текста запроса для каждого параметра автоматически создается объект TParam. Эти объекты доступны в специализированном редакторе, который вызывается при щелчке на кнопке свойства Params в Инспекторе объектов (рис. 12.2). Для каждого параметра требуется установить тип данных, который должен согласовываться с типом данных соответствующего поля.
![](image/gl-12-2.gif)
Рис. 12.2. Специализированный редактор параметров запроса
Теперь для задания текущих ограничений по дате поступления можно использовать свойство Params:
procedure TForml.RunBtnClick(Sender; TObject);
begin
with Queryl do
begin
Close;
Params[0].AsDateTime := StrToDate(DatelEdit.Text);
Params[l].AsDateTime := StrToDate(Date2Edit.Text);
Open;
end;
end;
При щелчке на кнопке RunBtn при помощи параметров в запрос передаются текущие значения ограничений дат.
Значения параметров запроса можно задать и из другого набора данных. Для этого применяется свойство DataSource компонента набора данных. Указанный в свойстве компонент TDataSource должен быть связан с набором данных, значения полей которого требуется передать в параметры. Названия параметров должны соответствовать названиям полей этого набора данных, тогда свойство DataSource начнет работать. При перемещении по записям набора данных текущие значения одноименных параметров полей автоматически передаются в запрос.
Для иллюстрации работы этого свойства рассмотрим простой пример, главная форма которого представлена на рис. 12.3. Этот проект не имеет ни одной строки написанного вручную программного кода.
![](image/gl-12-3.gif)
Рис. 12.3. Главная форма проекта DemoQueryParams
Верхний компонент TDBGrid отображает данные из таблицы Orders базы данных DBDEMOS и связан через компонент OrdersSource типа TDataSource с компонентом запроса ordersQuery. Текст запроса выглядит так:
SELECT * FROM Orders
Нижний компонент TDBGrid отображает данные о покупателе и через компонент CustSource типа TDataSource связан с запросом CustSource, свойство SQL которого имеет следующий вид:
SELECT * FROM Customer WHERE CustNo=:CustNo
Обратите внимание, что название параметра соответствует названию поля номера покупателя из таблицы Orders.
Свойство DataSource компонента custQuery указывает на компонент OrdersSource.
Как только оба набора данных открываются, текущее значение из поля CustNo набора данных orders автоматически передается в параметр запроса компонента CustQuery.
Благодаря этому, для двух наборов данных реализована связь "один-к-одному" по полю номера поставщика.
И в завершение разговора о параметрах запросов рассмотрим свойства и методы класса TParams, составляющего свойство Params, и класса TParam, инкапсулирующего отдельный параметр.
Набор данных является образом таблицы
Набор данных является образом таблицы базы данных в приложении. Он содержит группу записей и обеспечивает их использование.
Класс TDataSet, инкапсулирующий функциональность набора данных, является базовым классом для всех технологий доступа к данным. На его основе созданы все основные компоненты, применяемые при разработке приложений баз данных. Условно их можно разделить на три группы:
компоненты таблиц; компоненты запросов; компоненты хранимых процедур. В этой главе рассмотрены важнейшие свойства, методы и структуры, реализованные в компонентах, инкапсулирующих набор данных.
Состояния набора данных
В процессе своего функционирования (от открытия методом Open и до закрытия методом close) набор данных может выполнять самые разнообразные операции. Можно просто перемещаться по записям, можно редактировать данные и удалять записи, можно проводить поиск по различным параметрам и т. д. При этом желательно, чтобы все операции выполнялись как можно быстрее и эффективнее.
Набор данных в любой момент времени находится в некотором состоянии, т. е. подготовлен к выполнению действий строго определенного рода. И для каждой группы операций набор данных выполняет ряд подготовительных действий.
Все состояния набора данных делятся на две группы.
К первой группе относятся состояния, в которые набор данных переходит автоматически, а также непродолжительные по времени состояния, сопровождающие функционирование полей набора данных (табл. 12.1). Во вторую группу входят состояния, которыми можно управлять из приложения, например, перевод набора данных в режим редактирования (табл. 12.2).Базовый класс TDataSet, инкапсулирующий свойства набора данных, позволяет изменять состояние, а также проверять текущее состояние набора данных.
Текущее состояние набора данных передается в свойство state, имеющее тип TDataSetState:
type TDataSetState = (dslnactive, dsBrowse, dsEdit, dslnsert, dsSetKey, dsCalcFields, dsFilter, dsNewValue, dsOldValue, dsCurValue, dsBlockRead, dsInternalCalc);
Для управления состояниями набора данных используются методы open, Close, Edit, Insert.
Таблица 12.1. Автоматические состояния набора данных
Константа состояния |
Описание |
dsNewValue |
Включается при обращении к свойству NewValue поля набора данных |
dsOldValue |
Включается при обращении к свойству OldValue поля набора данных |
dsCurValue |
Включается при обращении к свойству CurValue поля набора данных |
dsInternalCalc |
Включается при расчете значений полей, для которых FindKind = fklnternalCalc |
dsCalcFields |
Включается при выполнении метода onCalcFields |
dsBlockRead |
Включается механизм ускоренного перемещения по набору данных |
dsOpening |
Существует при открытии набора данных методом Open или свойством Active |
dsFilter |
Включается при выполнении метода OnFilterRecord |
Таблица 12.2. Управляемые состояния набора данных
Константа состояния
|
Метод
|
Описание
|
dslnactive
|
Close
|
Набор данных закрыт |
dsBrowse
|
Open
|
Данные доступны для просмотра, но недоступны для редактирования |
dsEdit
|
Edit
|
Данные можно редактировать |
dslnsert
|
Insert
|
К набору данных можно добавлять новые записи |
dsSetKey
|
SetKey
|
Включается механизм поиска по ключу. Также могут использоваться диапазоны |
Закрытый набор данных всегда имеет неактивное состояние dsinactive.
При открытии набор данных переходит в состояние просмотра данных dsBrowse. В этом состоянии по записям набора данных можно перемещаться и просматривать их содержимое, но редактировать данные нельзя. Это основное состояние открытого набора данных, из него можно перейти в другие состояния, но любое изменение состояния происходит через просмотр данных (рис. 12.4).
![](image/gl-12-4.gif)
Рис. 12.4. Схема изменения состояний набора данных
При необходимости редактирования данных набор должен быть переведен в состояние редактирования dsEdit, для этого используется метод Edit. После выполнения метода можно изменять значения полей для текущей записи. При перемещении на следующую запись набор данных автоматически переходит в состояние просмотра.
Для того чтобы вставить в набор данных новую запись, необходимо использовать состояние вставки dsinsert. Метод insert переводит набор данных в это состояние и добавляет на месте текущего курсора новую пустую запись. При переходе на другую запись, после проверки на уникальность первичного ключа (если он есть) набор данных возвращается в состояние просмотра.
Состояние установки ключа dsSetKey используется только в табличных компонентах при необходимости поиска методами FindKey и FindNext, а также при использовании диапазонов (метод setRange). Это состояние сохраняется до момента вызова одного из методов поиска по ключу или метода отмены диапазона. После этого набор данных возвращается в состояние просмотра.
Состояние просмотра по блокам dsBlockRead используется набором данных при реализации быстрого перемещения по большим массивам записей без показа промежуточных записей в компонентах отображения данных и без вызова обработчика события перемещения по записям. Для реализации быстрого перемещения по набору данных можно использовать методы DisableControls И EnableControls.
Список описаний индексов
Информация об индексах набора данных содержится в свойстве класса
TDataSet
property IndexDefs: TindexDefs;
В нем для каждого индекса создается структура TindexDef. Доступ к информации об индексах осуществляется через свойство
property Items[Index: Integer]: TindexDef; default;
являющееся списком объектов TindexDef.
Объекты типа TindexDef можно добавлять в список при помощи метода
function AddlndexDef: TindexDef;
Поиск объекта описания индекса осуществляет метод
function Find(const Name: String): TindexDef;
который возвращает найденный объект по заданному в параметре Name имени индекса.
Пара методов
function FindlndexForFields(const Fields: string): TindexDef;
function GetlndexForFields(const Fields: String;
Caselnsensitive: Boolean): TindexDef;
находит объект описания индекса по списку полей, входящих в индекс. Если индекс не найден, ищется первый индекс, начинающийся с указанных полей. Первый из этих двух методов в случае неудачного поиска генерирует исключительную ситуацию EDatabaseError, а второй возвращает nil.
Список IndexDefs обновляется автоматически при открытии набора данных. Но метод
procedure Update; reintroduce;
обновляет список описаний индексов без открытия набора данных.
Стандартные компоненты
Внимательный читатель заметил, что на рис. 12.1 набор компонентов для каждой из представленных технологий доступа к данным примерно одинаков. Везде есть компонент, инкапсулирующий табличные функции, компонент запроса SQL и компонент хранимой процедуры. И хотя все они имеют разных ближайших предков, тем не менее, функциональность подобных компонентов в различных технологиях почти одинакова.
Поэтому имеет смысл рассмотреть общие для компонентов свойства и методы, представив, что существуют некие виртуальные общие предки для таблицы, запроса и хранимой процедуры.
Примечание
Некоторые из описываемых ниже свойств и методов присутствуют не в каждой реализации компонентов.