7. Общие принципы работы с наборами данных

 

7.1. Понятие наборов данных *

7.1.1. Набор данных TTable *

7.1.2. Набор данных TQuery *

7.2. Состояния наборов данных *

7.3. Навигация по набору данных *

7.3.1. Общие положения *

7.3.2. Определение начала и конца набора данных *

7.3.3. Порядок следования и порядок сортировки записей *

7.3.4. Навигация по набору данных вниз *

7.3.5. Навигация по набору данных вверх *

7.3.6. Спонтанные перемещения по набору данных *

7.3.7. Реакция на изменение курсора набора данных *

7.3.8. Временное отключение визуализации при работе с НД *

7.4. Внесение изменений в НД *

7.4.1. Свойства, запрещающие или разрешающие изменять записи в НД *

7.4.2. Изменение текущей записи *

7.4.3. Добавление новой записи *

7.4.4. Запоминание изменений - метод Post *

7.4.5. Отмена сделанных изменений - метод Cancel *

7.4.6. Оценка изменения записи *

7.4.7. Реакция на изменение данных *

7.4.8. Удаление записи *

7.5. Сценарий обновления записей на одной форме с компонентом TDBGrid *

7.6. Закладки на записях НД *

7.7. Поиск записей в наборах данных *

7.7.1. Метод Locate *

7.7.2. Использование методов FindFirst, FindLast, FindNext, FindPrior *

7.7.3. Метод Lookup *

7.8. Фильтрация записей в наборах данных *

7.8.1. Свойство Filtered *

7.8.2. Событие OnFilterRecord *

7.8.3. Свойство Filter *

7.8.4. Свойство FilterOptions *

7.8.5. Навигация в неотфильтрованном НД между записями, удовлетворяющими фильтру *

7.9. Получение информации о полях *

7.9.1. Использование компонента TFieldDefs *

7.9.2. Использование свойств FieldCount и Fields *

7.9.3. Свойства DefaultFields, CacheBlobs, метод ClearFields *

7.9.4. Способы обращения к полям набора данных *

7.10. Блокировка таблиц в многопользовательском режиме *

7.11. Синхронизация содержимого наборов данных *

7.11.1. Синхронизация содержимого наборов данных в одном приложении *

7.11.2. Синхронизация содержимого наборов данных в разных приложениях *

7.12. Обработка ошибок смены состоянии набора данных *

7.13. Ограничения на значения полей *

7. Общие принципы работы с наборами данных

7.1. Понятие наборов данных

Под набором данных в Delphi понимается группа записей из одной или нескольких ТБД, доступная для использования через компоненты TTable или TQuery.

Как набор данных, рассматривается также компонент TStoredProc (хранимая процедура в серверной базе данных, возвращающая набор данных). Однако применение компонента TStoredProc ограничено. Во-первых, он может применяться только для серверных (удаленных) БД; во-вторых, только для тех из них, которые поддерживают механизмы хранимых процедур, возвращающих набор данных. Поэтому в дальнейшем при рассмотрении наборов данных, будем ориентироваться в основном на компоненты TTable и TQuery.

Если рассматривать иерархию компонентов Delphi, компоненты TTable и TQuery являются наследниками компонента TDBDataSet, потомка компонента TBDEDataSet, который, в свою очередь, является наследником компонента TDataSet.

Компоненты TTable и TQuery имеют общие свойства, методы и события;

это обусловливается тем, что они имеют общих "родителей". Именно эти свойства, методы и события рассматриваются в настоящем разделе. При этом TTable и TQuery называются общим термином "набор данных" (НД). В литературе по Delphi TTable и TQuery при рассмотрении их общих свойств часто называют типом DataSet, по имени типа их предка.

TTable и TQuery имеют также свойства, методы и события, присущие только TTable или только TQuery. Такие свойства, методы и события для каждого из данных компонентов рассматриваются далее в отдельных главах, посвященных каждому из указанных компонентов.

Перед рассмотрением общих возможностей для работы с НД разберем общие черты и различия TTable и TQuery.

7.1.1. Набор данных TTable

TTable содержит записи, источником которых может быть только одна ТБД. Состав записей зависит от того, производится ли в TTable фильтрация по какому-либо условию. Если нет, то в TTable будут представлены все записи ТБД, ассоциированной с данной TTable; если да, то в TTable попадут только записи, удовлетворяющие условию фильтрации.

По той причине, что одна и та же TTable может в один момент времени содержать неотфильтрованные записи, в другой - отфильтрованные по одному условию и в третий момент времени - записи, отфильтрованные по третьему условию, считают, что НД TTable содержит подмножество множества записей ТБД, которая ассоциирована с данным TTable.

Псевдоним БД указывается в свойстве DatabaseName, имя ассоциированной ТБД - в свойстве TableName.

7.1.2. Набор данных TQuery

TQuery содержит записи, источником которых могут являться несколько ТБД, а также агрегированные значения (такие как сумма, минимум, максимум, среднее), просчитанные по полям одной или нескольких таблиц.

НД формируется так: выполняется SQL-запрос, представленный оператором SELECT (свойство SQL), и в качестве НД возвращаются записи из таблиц-источников, удовлетворяющие определенным условиям (если они имеются).

ТБД-источники перечисляются в разделе FROM оператора SELECT. Условия выборки записей указываются в разделе WHERE. Записи результирующего НД состоят из полей, перечисленных после ключевого слова SELECT (или, если указан символ * - всех полей). Например:

SELECT T.NumTeacher, S.NumStudent, T.ExamDate

FROM teachers T, students S WHERE (T.Kurs = S.Kurs)

В данном случае будет возвращен НД, состоящий из записей, источником которых служат ТБД teachers и students. Эти записи должны удовлетворять условию равенства поля Kurs обеих ТБД. В результирующем НД будет всего 3 поля - NumTeacher, ExamDate из teachers и NumStudent из students.

Более подробно об операторе SELECT и иных SQL-операторах см. соответствующие разделы во второй части книги.

Псевдоним БД указывается в свойстве DatabaseName. В случае, если псевдоним указан, все ТБД в разделе FROM оператора SELECT считаются принадлежащими данной БД; в противном случае для каждой ТБД необходимо указывать маршрут поиска, а если он не указан, ТБД должны располагаться в текущем каталоге.

Как и в случае использования TTable, НД TQuery может содержать полное множество записей какой-либо ТБД. В этом случае, являющемся, вообще говоря, частным, в качестве списка полей следует указать символ "*", выборка должна вестись из одной ТБД и должно отсутствовать условие фильтрации в операторе SELECT.

7.2. Состояния наборов данных

НД могут находиться в одном из 6 состояний:

dslnactive

НД закрыт.

dsBrowse

Состояние по умолчанию для открытого НД. Показывает, что записи просматриваются, но в данный момент не изменяются.

dsEdit

НД находится в состоянии редактирования текущей записи (после явно или неявно вызванного метода Edit).

dslnsert

НД находится в состоянии добавления новой записи (после явно или неявно вызванного метода Insert или Append).

dsSetKey

НД находится в состоянии поиска записи по критерию, заданному методами FindKey, GotoKey, FindNearest или GotoNearest. По окончании поиска НД переходит в состояние dsBrowse.

dsCalcFields

Выполняется установление значений вычисляемых полей (по алгоритму, заданному в обработчике события OnCalcFields). В данном режиме изменения в НД вноситься не могут. После выхода из режима НД переходит в предыдущее состояние.

dsFilter

Обрабатывается фильтрация записей в НД при свойстве Filtered, установленном в True. Имеет место текущий вызов события OnFilterRecord для определения того, удовлетворяет ли текущая запись условию фильтрации, описанному в обработчике данного события. После выполнения события OnFilterRecord НД переводится в состояние dsBrowse.

Рассмотрим методы, которые могут переводить БД из одного состояния в другое.

• Inactive—>dsBrowse НД во время выполнения программы можно открыть методами Table. Open, Query. Open. Во время разработки и во время выполнения НД можно открыть, установив в True свойства Table. Active и Query. Active.

dsBrowse—>Inactive НД во время выполнения программы можно закрыть методами Table. Close, Query. Close. Во время разработки и во время выполнения НД можно закрыть, установив в False свойства Table.Active и Query.Active.

Заметим, что если какая-либо запись на момент закрытия НД находится в режиме редактирования (dsEdit) или добавления новой записи (dslnsert), применение метода Close не приводит к автоматической выдаче метода Post. Таким образом, НД закрывается, находясь в режимах dslnsert или dsEdit, а не dsBrowse. В этом случае изменения, сделанные в записи, не запоминаются. Для перевода НД из указанных режимов в режим dsBrowse, перед тем как НД закрывается, используйте обработчик события BeforeClose. Заметим, что описанная ситуация будет встречаться в первую очередь для внезапно или принудительно закрываемых НД.

• dsBrowse—>dsEdit Перевести НД в режим редактирования можно методом Edit. После этого значения полей текущей записи можно изменять.

• dsEdit—> dsBrowse Метод Post приводит к запоминанию измененной записи в НД. Метод Cancel отменяет изменения, сделанные в полях записи. Запись не запоминается в НД.

• dsBrowse—>dslnsert Перевести НД в режим вставки можно методами Insert или Append, После этого программе становится доступна пустая запись, полям которой нужно присвоить какие-либо значения. Чтобы полям новой записи присвоить умалчиваемые значения , следует воспользоваться обработчиком события OnNewRecord.

dslnsert—> dsBrowse Метод Post добавляет новую запись в НД. Если НД не находится в режиме dslnsert, возбуждается исключительная ситуация. Метод Cancel отменяет добавление новой записи в НД. Содержимое полей, назначенных новой записи, теряется.

• dsBrowse—>dsSetKey НД находится в данном состоянии, когда осуществляется поиск записи, удовлетворяющей условию, установленному методом SetKey (и затем, возможно, измененному методом EditKey). Именно эти методы и переводят НД в режим dsBrowse. Поиск записи производится одним из следующих методов: GoToKey, GoToNearest, FindKey, FindNearest. В случае успешного или неуспешного завершения метода поиска, НД переводится в состояние dsBrowse.

• dsBrowse—>dsFilter НД находится в данном состоянии всякий раз, когда приложение обрабатывает событие OnFilterRecord при фильтрации записей (при свойстве Filtered = True). При этом НД переводится из состояния dsBrowse в состояние dsFilter. Это предотвращает модификацию НД во время фильтрации. После завершения вызова обработчика события OnFilterRecord НД переводится в состояние dsBrowse. Вызов события OnFilterRecord производится для каждой записи НД при установке свойства Filtered в состояние True.

Получить текущее состояние НД можно, используя метод State. Он возвращает следующие константы: dslnactive, dsBrowse, dsEdit, dslnsert, dsSetKey, dsCalcFields, dsFilter.

Пример IF Tablel.State = dslnactive THEN Table1.Active := True;

Реакция на изменение состояния набора данных. Событие OnStateChange (компонент DataSource) наступает всякий раз при изменении состояния НД. Следующий пример показывает, как отобразить на экране (в компоненте Label 1) сообщение о текущем состоянии НД:

procedure TForm1.DataSourcelStateChange(Sender: TObject);

var S : String;

begin

CASE Table1.State OF

dslnactive S = 'He активна' ;

dsBrowse S = 'Просмотр' ;

dsEdit S = 'Редактирование';

dslnsert S = 'Вставка';

dsSetKey S = 'Установка ключа' ;

dsCalcFields S = 'Вычисляемое поле' ;

END; {case}

Label1.Caption := S;

end;

Свойства некоторых компонентов, в первую очередь Enabled, могут зависеть от состояния НД. Например, если кнопка Button 1 должна быть доступной для нажатия только в режиме dslnsert, допустим такой код:

procedure TFormI.DataSourcelStateChange(Sender: Tobject) ;

begin

Button1.Enabled := (Tablel.State = dslnsert);

end;

7.3. Навигация по набору данных

7.3.1. Общие положения

Существует два способа работы с записями в НД.

Способ, основанный на использовании операторов SQL, предполагает оперирование группами записей. Именно так работают SQL-операторы группового обновления НД UPDATE, INSERT, DELETE и выборки групп записей SELECT. Записи, удовлетворяющие некоторому условию, выдаются группами; даже если условию удовлетворяет только одна запись, считается, что в данном случае группа состоит из одной записи.

Второй способ состоит в оперировании единичными записями. Если необходимо изменить, добавить или удалить группу записей, необходимая операция выполняется для каждой из таких записей. Для этого такие записи в НД нужно отыскать, для чего применяются навигационные методы. Они всегда работают с единичной записью и связанны с понятием курсора НД.

Понятие курсора набора данных. Под курсором набора данных понимается указатель текущей записи в конкретном наборе данных. Текущая запись - та запись, над которой в данный момент времени можно выполнять какие-либо операции (удаление, изменение, чтение значений, содержащихся в записи полей).

Существует 5 методов для изменения курсора НД:

Procedure First;

Устанавливает курсор на первую запись в наборе данных.

procedure Last;

Устанавливает курсор на последнюю запись в наборе данных.

procedure Next;

Перемещает курсор на следующую запись в наборе данных.

procedure Prior;

Перемещает курсор на предыдущую запись в наборе данных.

function MoveBy(n:Integer): Integer;

Перемещает курсор на n записей к'концу набора данных (n > 0) или к началу набора (n < 0.)

7.3.2. Определение начала и конца набора данных

Свойство property BOF: Boolean;

Возвращает True, если курсор установлен на первую запись в наборе данных.

Свойство property EOF: Boolean;

извращает True, если курсор установлен на последнюю запись в наборе данных.

7.3.3. Порядок следования и порядок сортировки записей

Может сложиться впечатление, что первая и последняя записи набора Данных всегда фиксированы, что это физически первая и последняя записи в наборе данных. Это неверно. Во-первых, как уже отмечалось выше, набор данных может содержать часть записей из ТБД. Поэтому набор данных -понятие логическое, а не физическое. Для TTable последовательность расположения записей в наборе данных определяется используемьш индексом, обуславливающим сортировку. Для TQuery порядок следования записей либо случаен, особенно при использовании в качестве источника более одной ТБД, либо упорядочен в порядке перечисления полей, в разделе ORDER BY.

Начиная работать с НД, имеющим одну сортировку записей, мы можем затем переопределить сортировку, и записи "перестроятся" в соответствии с новой сортировкой, т.е. логический порядок их следования изменится.

При изучении вопросов навигации по НД следует говорить прежде всего о Логическом характере следования записей, поскольку физический характер их расположения в конкретном случае неизвестен.

Пусть, например, имеется ТБД, состоящая из 2 полей: Name (Фамилия сотрудника) и Oklad (Оклад). Пусть в ТБД имеется 10 записей, которые вводились следующим образом и. следовательно, в таком порядке и хранятся:

Пусть данная ТБД проиндексирована по 2 индексам. Первый - по возрастанию поля Name, второй - по возрастанию поля Oklad.

Пусть в наборе данных, связанном с этой ТБД, установлен фильтр на поле Oklad (см. метод SetRange). Значение в этом поле должно лежать в диапазоне 500..1500. Если порядок прохождения записей определяется индексом по полю Name (Tablel.IndexfieldNames := 'Name'), получим следующий НД (рис. 7.1):

Если порядок прохождения записей определяется индексом по полю Oklad. получим следующий НД (рис. 7.2):

Как можно заметить, при сортировке по полю Name метод Last установит курсор на запись со значением поля Name, равным 'Якунин'; при сортировке по полю Oklad - на запись со значением поля Name, равным Name=' Юрьев'. Аналогично, если текущей является 3-я по счету запись от начала НД, для первого случая это будет 'Юрьев', для второго - 'Якунин'. Метод Next переместит курсор для первого случая на запись 'Иванов', для второго - на запись 'Якунин'.

7.3.4. Навигация по набору данных вниз

Для выполнения действий, начиная от некоторой стартовой записи и до конца набора данных, используют цикл WHILE not EOF. Стартовая запись может устанавливаться методом FindKey (см. ниже). Приведем пример для случая, когда стартовая запись - первая в наборе:

WITH Tablel do begin First;

WHILE not EOF do begin

{Какие-либо действия}

Next;

END; {while]

END; {with}

7.3.5. Навигация по набору данных вверх

Для выполнения каких-либо действий, начиная от некоторой стартовой записи и до начала набора данных, используют цикл WHILE not BOF. Стартовая запись может устанавливаться методом FindKey (см. ниже). Приведем пример для случая, когда стартовая запись - последняя в наборе:

WITH Tablel do begin Last;

WHILE not BOF do begin

{Какие-либо действия}

Prior;

END; {while}

END; {with}

7.3.6. Спонтанные перемещения по набору данных

Вообще говоря, часто необходимо в зависимости от каких-либо условий "прыгать" по НД взад-вперед. Поэтому распространен вариант одновременного использования Next, Prior и MoveBy в одном программном блоке. При этом важно помнить о том обстоятельстве, что применение метода Edit, когда изменяется значение индексного поля, по которому в настоящий момент ведется сортировка в НД, может переместить запись вниз или вверх. Например, для приводимой выше таблицы, состоящей из полей Name и Oklad, выполним следующий код, осуществляющий увеличение окладов сотрудников на 1000:

Tablel.IndexFieldnames := 'Oklad';

WITH Tablel do begin

First;

WHILE not EOF do begin

Edit;

TablelOklad.Value := TablelOklad.Value + 1000;

Post;

Next;

END;//while

END; //with

Нетрудно убедиться, что указанный фрагмент не будет правильно работать: оклад господина Яковлева (500) после увеличения на 1000 составит 1500, поэтому запись переместится в НД после записи 'Юрьев', в то же время оставаясь текущей. После запоминания измененной записи (Post) будет предпринята попытка перейти к следующей записи (Next). Однако наша запись, прежде логически первая, после изменения поля Oklad стала логически последней. Поскольку курсор НД находится на последней записи, свойство Tablel.EOF будет автоматически установлено в True. Поэтому метод Next выполнен не будет. На новом шаге цикла WHILE (2-м по счету) произойдет остановка цикла из-за выполнения условия прекращения цикла (рис. 7.3):

Поэтому следует всегда придерживаться правила: не изменять значения индексного поля при прохождении набора данных в цикле.

Можно предложить способ решения данной проблемы, который состоит в простом переборе записей при отключенной сортировке по исходному индексу или при сортировке по другому индексу. Например, для описанного выше НД следующий код будет правильным (результат работы - на рис. 7.5.):

var OldIndexFieldnames : String;

begin

// запомним старое индексное поле

OldIndexFieldnames := Tablel.IndexFieldnames;

// назначим индекс по неизменяемому полю

Tablel.IndexFieldnames := 'Name';

WITH Tablel do begin

First;

WHILE not EOF do begin

Edit;

TablelOklad.Value := TablelOklad.Value + 1000;

Post;

Next;

END;//while

END; //with

// восстановим старое индексное поле

Tablel.IndexFieldnames := OldIndexFieldnames;

IF OldIndexFieldnames = 'Oklad' THEN

TablelOklad.Index := 0;

Поскольку значение ключевого поля не изменяется, порядок следования записей остается прежним.

Рассмотрим другой способ. Он состоит в применении к набору данных временной фильтрации.

ЗАМЕЧАНИЕ. Данный способ приводится лишь для того, чтобы показать, что могут иметь место и другие подходы к разрешению указанной проблемы. Однако лучше не привыкать к подобному "хитроумию", поскольку оно оправдано лишь когда проблему невозможно разрешить другими способами. Способ изменения текущего индекса, рассмотренный выше, проще и безопаснее. Второй способ имеет более узкое применение, поскольку в этом случае индексное поле должно иметь у всех записей одно и то же значение. Рекомендую повторно вернуться к его рассмотрению после того, как вы ознакомитесь с фильтрацией записей в НД при помощи свойства Filtered, которое описано ниже Пусть в НД для рассмотренного выше примера имеется поле Otdel. Пусть текущий индекс в НД - также построен по полю Otdel. Имеются 2 отдела: Х и Z. Пусть необходимо сменить название отдела Х на Y.

Разместим в форме 2 компонента TEdit - OldOtdelEdit, для указания имени отдела, которое требуется заменить, и NewOtdelEdit, для указания нового имени отдела (рис. 7.6.):

Для набора данных Tablel определим обработчик события OnFilterRecord

procedure TFormI.Table1FilterRecord(DataSet: TDataSet;

var Accept: Boolean);

begin

Accept := TablelOtdel.AsString = OldOtdelEdit.Text;

end;

Этот обработчик будет фильтровать в НД Tablel только те записи, у которых поле Otdel содержит старое значение отдела.

Поясним, что в примере используется свойство набора данных

property RecordCount: Integer;

Оно возвращает текущее число записей в НД.

Определим обработчик события нажатия кнопки "Все записи - изменить код отдела":

procedure TFormI.Button4Click(Sender: TObject);

begin

// включим фильтрацию по старому имени отдела

Tablel.Filtered := True;

//последовательно перебираем записи

WITH Tablel do begin

First;

WHILE not (RecordCount = 0) do begin

Edit;

//изменяем название отдела

TablelOtdel.Value := NewOtdelEdit.Text;

//после запоминания изменений запись перестает

//удовлетворять условиям фильтрации и "исчезает"

//из набора данных. После выполнения каждого метода Post

//отфильтрованный набор данных уменьшается на одну запись

Post;

END;//while

END; //with

// отменим фильтрацию

Tablel.Filtered := False;

end;

Заметим, что в цикле нет перехода к новой записи с помощью метода Next. Дело в том, что перед началом изменения значения поля Otdel включается фильтрация. После того, как значение поля изменилось, запись перестает удовлетворять условию фильтрации, поскольку фильтрация ведется по старому значению поля Otdel. Такая запись, не удовлетворяющая условию фильтрации, в НД не входит. После того, как все записи изменены, фильтрация отменяется. Отметим также, что условием окончания цикла WHILE сделан факт отсутствия в НД записей (при включенной фильтрации это записи, удовлетворяющие условию фильтрации).

Результаты работы показаны на рис. 7.7. и 7.8.

Такой способ часто применим при работе со связанными НД, родительским и дочерним. Заметим, что фильтрация в данном примере обеспечивается свойством Filtered, общим для TTable и TQuery. Однако можно применять и иные средства фильтрации, например, метод SetRange компонента TTable.

7.3.7. Реакция на изменение курсора набора данных

Событие OnDataChange (компонент DataSource) возникает всякий раз при изменении курсора НД, т.е. при переходе к новой текущей записи. Это событие возникает, когда курсор БД уже находится на новой записи.

Событие происходит и в режимах dslnsert и dsEdit:

- при изменении какого-либо поля;

- при первом перемещении с измененного поля на другое поле. Два события компонента типа "набор данных" также происходят при переходе к новой записи:

property BeforeScroll: TDataSetNotifyEventI;

Событие наступает перед переходом на другую запись в наборе данных. property AfterScroll: TDataSetNotifyEventI;

Событие наступает после перехода на другую запись в наборе данных.

7.3.8. Временное отключение визуализации при работе с НД

При выполнении действий с НД, влекущих за собой частое изменение местоположения курсора БД, в визуальном компоненте, показывающем записи (например, TDBGrid) или текущую запись (TDBEdit и др.), будет возникать эффект "прокрутки" записей. Он не всем нравится. Кроме этого, при смене местоположения курсора БД (т.е. при смене текущей записи НД) необходимо время для отражения произошедших изменений в визуальном компоненте.

Для устранения данной проблемы имеются методы procedure DisableControls; procedure EnableControls;

Первый отключает связь с визуальным компонентом, а второй -восстанавливает ее. Например, при последовательном переборе записей НД, произведенном таким образом:

WITH Tablel do begin

DisableControls;

First;

WHILE not EOF do begin

{Какие-либо действия}

Next;

END; {while}

EnableControls;

END; {with}

в таблице компонента DBGridI не будет видно эффекта прокрутки записей. Наоборот, у пользователя возникнет иллюзия, что курсор БД сразу переустановился с текущей записи набора данных на его последнюю запись.

7.4. Внесение изменений в НД

7.4.1. Свойства, запрещающие или разрешающие изменять записи в НД

Свойство property CanModify: Boolean;

набора данных определяет, может ли НД переводиться в состояние dslnsert и dsEdit (CanModify= True) или не может (CanModify =False). Это свойство зависит от значения свойства Readonly набора данных. Если Read0nly= True, CanModify автоматически переводится в False. Когда Read0nly= False, CanModify может принимать значения как True, так и False, устанавливая таким образом возможность изменения НД в зависимости от каких-либо условий.

Свойство AutoEdit компонента TDataSourse, связанного с данным НД, определяет, возможен ли (True) автоматический перевод НД в состояние dsEdit, или невозможен (False). В последнем случае для изменения НД программа должна вызвать метод Edit . Свойство AutoEdit не влияет на возможность перевода в состояние dslnsert. Для того, чтобы запретить НД переход в режим dslnsert, достаточно либо сделать НД открытым только для чтения (свойство НД ReadOnly = True), либо установить режим НД Readonly = True для полей, входящих в состав первичного ключа. В этом случае корректировка записи будет возможна, за исключением указанных полей.

НД автоматически может переводиться в состояние dslnsert или dsEdit, если пользователь в визуальном компоненте, связанном с НД, выполняет определенные действия. Вид этих действий зависит от визуального компонента, связанного с НД.

Например, для перехода в режим dsEdit в компоненте TDBGrid, связанном с набором данных, достаточно изменить значение любого поля; в компонентах TDBEdit или TDBMemo, связанных с отдельными полями НД, следует изменить значение поля, с которым связаны TDBEdit или TDBMemo; для компонента DBNavigator, связанного с данным НД, нужно нажать соответствующую кнопку и т.д.

7.4.2. Изменение текущей записи

Чтобы изменить запись в НД, этот НД нужно перевести методом Edit из состояния dsBrowse в состояние dsEdit, затем произвести изменение значения одного или нескольких полей записи и использовать метод Post для запоминания измененной записи в НД. Post в данном случае при благополучном исходе переводит НД из состояния dsEdit в состояние dsBrowse.

Для отказа от запоминания измененной записи в НД используется метод Cancel. Он также переводит НД из состояния dsEdit в состояние dsBrowse.

Метод Edit Редактирование записи должно быть разрешено (свойство property ReadOnly: Boolean; должно быть установлено в False). Помимо этого, могут быть запрещены для корректировки отдельные поля записи (когда свойство Readonly соответствующих компонентов TField установлено в True). Метод Edit может вызываться: программно, автоматически, когда пользователь в визуальном компоненте, связанном с НД, выполняет определенные действия. Вид этих действий зависит от визуального компонента.

Автоматический перевод набора данных в режим редактирования должен быть разрешен свойством AutoEdit соответствующего компонента DataSource (значение True).

Пример. Изменение значения поля Oklad в текущей записи набора данных, запоминание изменений. Новое значение оклада вводится пользователем в поле ввода в форме (компонент TEdit с именем Editi). Введенное пользователем значение доступно через свойство Editl.Text. Это свойство строкового типа, поэтому оно должно быть преобразовано в целочисленный вид перед присваиванием его числовому полю Oklad.

WITH Tablel. do begin

Edit;

FieldByName('Oklad').Value := IntToStr(Editl.Text);

Post;

END;//with

Метод SetFields

Метод

procedure SetFields(const Values: array ofconst);

объединяет функциональность методов Edit, Post и действий по присваиванию значений полям изменяемой записи. В ходе выполнения метода сначала НД переводится в режим dsEdit. Затем полям записи присваиваются значения, перечисленные в открытом массиве Values При этом первое значение в списке присваивается первому полю, второе - второму и т.д Естественно, что значения в списке должны быть совместимы с теми полями, которым они присваиваются. Например, попытка присвоить полю типа Real символьное значение приведет к возбуждению исключительной ситуации

Если в списке число значений меньше числа полей записи, те поля, которым "не хватило" значений, сохраняют свое первоначальное значение. Наоборот, если значений больше, чем нужно для заполнения полей, "лишние" значения теряются.

После успешного присваивания значения полям записи автоматически выполняется метод Post.

Метод SetFields обычно выполняется после того, как пользователь введет значения в переменные и будет произведен контроль правильности их значений

Пример. Table1.SetFields(['Петров', 'Бухгалтер']) ;

7.4.3. Добавление новой записи

Чтобы добавить новую запись в НД, нужно вызвать метод Insert для перевода НД из состояния dsBrowse в состояние dsEdit Затем производится присваивание значения одному или нескольким полям записи, после чего выполняется метод Post для запоминания новой записи в НД Post при благополучном исходе переводит НД из состояния dsEdit в состояние dsBrowse

Для отказа от запоминания новой записи в НД используется метод Cancel Он также переводит НД из состояния dsEdit в состояние dsBrowse.

Метод Insert При добавлении записи изменение НД должно быть разрешено (свойство Readonly должно быть установлено в False). Помимо этого, могут быть запрещены для изменения отдельные поля записи (когда свойство Readonly соответствующих компонентов TField установлено в True). В этом случае в них нельзя ввести новые значения.

Метод Insert может вызываться: программно, автоматически, когда пользователь в визуальном компоненте, связанном с НД, предпринимает соответствующие действия. Для перехода в режим dslnsert в компоненте TDBGrid достаточно нажать на клавиатуре клавишу Insert или, находясь на последней записи НД, попытаться перейти на нижнюю, несуществующую запись. То же происходит при нажатии соответствующей кнопки связанного с данным НД компонента TDBNavigator,.

Пример. Добавление записи

Table1.Insert;

// установка значений полей добавляемой записи

Tablel.Post;

Метод Append

Метод procedure Append; аналогичен методу Insert, но он добавляет запись в конец набора данных, в то время как Insert добавляет ее после текущей записи.

Для индексированных НД применение метода Append приводит к тем же последствиям, что и применение метода Insert.

Метод InsertRecord

Метод procedure InsertRecord(const Values: array ofconst); объединяет функциональность методов Insert, Post и действий по присваиванию значений полям новой записи. В функциональном отношении он полностью аналогичен методу SetFields (см. выше).

Метод AppendRecord

Метод procedure AppendRecord(const Values: array of const); отличен от метода InsertRecord только тем, что помещает новую запись не после текущей записи, а вслед за последней записью НД.

7.4.4. Запоминание изменений - метод Post

Выполнение метода Post приводит к запоминанию изменений, сделанных в режиме добавления или изменения записи.

Если НД не находится в режиме dslnsert или dsEdit, применение Post приводит к возбуждению исключительной ситуации. Вызов Post зависит от способа, которым ранее был вызван метод Insert или Edit: программно; автоматически.

Post обычно вызывается автоматически, если пользователь предпринимает соответствующие действия, направленные на запоминание измененной записи в НД. Вид этих действий зависит от визуального компонента, связанного с НД. Например, для компонента TDBGrid, связанного с набором данных, это -переход к другой записи. Для НД, управляемого компонентом TDBNavigator, это - нажатие соответствующей экранной клавиши компонента TDBNavigator. Реже изменения в наборе данных, автоматически переведенном в режим редактирования, запоминаются путем программного вызова метода Post.

Метод Post, независимо от того, вызывается он программно или автоматически, может завершиться неудачно. Причиной этого могут послужить неверные значения в соответствующих полях записи. Например:

• поле обязательного заполнения (свойство Required = True у соответствующего компонента TField) содержит пустое значение;

• для ТБД, для которой определен уникальный ключ, возникла ситуация дублирования ключа (Key Violation), то есть ключевое поле (группа полей) данной записи содержит значение, которое уже хранится в этом поле (группе полей) в другой записи;

обработчики событий типа OnValidate (компонент TField) или BeforePostRecord обнаружили, что какое-либо поле содержит неверное значение, не удовлетворяющее некоторым условиям. В этом случае программно возбуждается исключительная ситуация, которая подавляет выполнение Post.

В лучшем случае при возникновении препятствий для выполнения Post запись переводится в состояние, в котором НД находился до выполнения метода (dslnsert или dsEdit).

7.4.5. Отмена сделанных изменений - метод Cancel

Метод Cancel отменяет все изменения, сделанные в записи. Если НД находился в режиме добавления новой записи, запись в НД не добавляется. Если НД находился в режиме изменения записи, изменявшаяся запись в НД не записывается и данные в ней остаются в том состоянии, в котором они находились до перехода в режим dsEdit. Сам НД переводится в режим dsBrowse. Вызов Cancel зависит от способа, которым ранее был вызван метод Insert или Edit: программно; автоматически.

Cancel вызывается автоматически, если пользователь предпримет соответствующие действия, направленные на запоминание измененной записи в НД. Вид этих действий зависит от визуального компонента, связанного с НД. Например, для компонента TDBGrid, связанного с набором данных, это нажатие клавиши Esc. Для НД, управляемого компонентом TDBNavigator, это нажатие соответствующей экранной клавиши компонента TDBNavigator.

7.4.6. Оценка изменения записи

Часто бывает необходимо знать, вносились ли в запись изменения в режимах dslnsert или dsEdit. Это актуально в тех случаях, когда внесение изменений в поля записи зависит от каких-либо условий, которые могут наступать или не наступать в разные моменты работы приложения. Свойство НД property Modified: Boolean; автоматически устанавливается в True, если значение какого-либо поля записи НД было изменено в режимах dslnsert или dsEdit. Методы Post и Cancel переводят свойство в состояние False.

Пример. В следующем фрагменте запись будет запомнена в НД, только если в нее вносились изменения:

Table1.Edit;

IP Tablel.Modified THEN Post ELSE Cancel;

7.4.7. Реакция на изменение данных

Событие On UpdateData (компонент DataSource) возникает для измененной (или вновь добавляемой) записи, когда выполнен метод Post, но физическое перезаписывание измененной записи в ТБД еще не произошло.

Событие On Validate (компонент TField) возникает после любого изменения значения поля, произведенного вручную или программно (это относится и к вводу значения в поле при создании новой записи). Это событие служит для контроля правильности значений поля, если на него накладываются какие-либо ограничения. Событие возникает перед выполнением метода Post, физически записывающего измененную запись в ТБД. В случае несоответствия значения поля накладываемым ограничениям выполнение Post (и, следовательно, физическое запоминание в БД записи с неверным полем) можно предотвратить, используя метод Abort или принудительно возбудив исключительную ситуацию (raise Exception. Create). Например,

procedure TForm1.TableKodIzdeliaValidate(Sender: TField);

begin

IF TableKodIzdelia.AsInteger > 1000 THEN raise Exception.Create('Неверное значение кода изделия');

end;

 

7.4.8. Удаление записи

Удаление текущей записи в наборе данных реализуется методом Delete. Например: Table1.Delete;

Удаление записи может производиться: программно; автоматически, если это предусмотрено в том или ином компоненте. Так, в компоненте TDBGrid нажатие комбинации клавиш Ctrl + Del влечет за собой удаление записи, которое, в соответствии с опциями настройки TDBGrid, может выполняться как с запросом подтверждения, так и без него.

Необходимо помнить об одной важной особенности. Записи в различных СУБД могут удаляться 2 способами:

пометка записи в ТБД как удаленной. Сама запись физически не удаляется из НД. В зависимости от СУБД новые записи могут записываться на место помеченных как "удаленные" или в конец ТБД. В последнем случае такие ТБД могут "разбухать" до больших размеров, поэтому время от времени для них проводят операцию сжатия, при которой помеченные как удаленные записи физически уничтожаются, а остальные записи "сдвигаются" вверх, заполняя образовавшиеся пустоты в ТБД. немедленное удаление записей из ТБД, вследствие чего последующие записи "сдвигаются" вверх, заполняя образовавшиеся в ТБД пустоты. В Delphi при работе с НД реализован второй метод. После удаления записи все оставшиеся записи "сдвигаются" наверх. При удалении одной записи это может быть несущественным, однако, если нужно удалить несколько записей, это способно внести осложнения. Например, пусть требуется удалить все записи в Tablel. Можно было бы предположить, что данную потребность можно реализовать следующим программным кодом:

WITH Tablel do begin

First;

WHILE not EOF do begin

Delete;

Next; // Ошибка!

END;//while

END;//with

Однако в действительности этот код приведет к удалению примерно половины записей в Tablel. Причина этого лежит в том, что когда мы удаляем запись (например, № 3), последующие записи автоматически перемещаются вверх, и поэтому запись, бывшая до удаления следующей (№ 4), становится текущей (№ 3). После выполнения метода Next осуществляется переход к записи № 4. Таким образом, записи удаляются через одну (см. рис. 7.9 и 7.10).

Удалив ненужный вызов Next, мы сотрем все записи:

WITH Tablel do begin

First;

WHILE not (RecordCount = 0) do

Delete;

END;//with

Чаще всего нужно удалять не все записи НД а часть записей, удовлетворяющих некоторому условию Рассмотрим два способа

ЗАМЕЧАНИЕ Я рекомендую вернуться к повторному рассмотрению этих способов после того как вы ознакомитесь

- с фильтрацией записей в НД при помощи свойства Filtered

- с поиском записей в НД при помощи метода Locate

Материал о них приведен в данномраздече ниже

Во-первых, можно воспользоваться временной фильтрацией удаляемых записей в момент группового удаления (См п 7 3 6)

Второй способ состоит в использовании метода Locate, реализующего поиск необходимой записи по точному соответствию значений некоторых полей

Метод Locate указывает поля, по которым ведется поиск, и значения этих полей, по которым нужно найти записи В нашем случае это поле Otdel и свойство OtdelEdit Text (компонент TEdit), где содержится наименование отдела, по которому нужно удалить все записи в НД Если запись с таким значением поля Otdel найдена, Locate возвращает True, в противном случае

False Как только запись найдена она удаляется, когда Locate возвратит False это значит что все записи с указанным наименованием одела удалены (см рис 7 13 и 7 14)

// Обработчик нажатия кнопки 'Удалить все записи с указанным отделом"

procedure TFormI Button4Click(Sender TObject);

begin

WITH Tablel do begin

First;

WHILE Locate('Otdel', OtdeiEdit Text,[]) do Delete;

END; //with

end;

ЗАМЕЧАНИЕ. Для больших НД, а также если в процессе удаления в НД меняются индексы, определяющие текущую сортировку, рекомендуется перед реализацией удаления отключать связь НД с управляющими компонентами, а по окончании удаления - включать-

WITH Tablel do begin

DisableControls;

EnableControls;

END; {with}

7.5. Сценарий обновления записей на одной форме с компонентом TDBGrid

Записи набора данных периодически добавляются, изменяются и удаляются Если выполнение указанных действий ложится на пользователя, в приложении должны быть реализованы удобный интерфейс добавления, изменения и удаления записей

Несомненно, действия над набором данных в этом случае тесно связаны с вопросами реализации пользовательского интерфейса в приложении Материал именно такого рода содержится в данном разделе Он будет полезен начинающим разработчикам или тем, кто только начинает изучать Delphi, поскольку показывает, что соединение пользовательского интерфейса и действий над набором данных не является чем-то исключительно сложным

Если Вас не интересует, как можно реализовать в форме интерфейс пользователя для внесения изменений в набор данных, Вы можете пропустить данный раздел

Существует несколько вариантов внесения изменений в набор данных в приложении-

• изменения реализуются при выполнении программного кода Как правило, такой способ внесения изменений в НД используется для транзакционных таблиц, т е таблиц, данные в которых формируются приложением автоматически Например, в транзакционную таблицу "Суммарный отпуск товаров" приложением автоматически вносятся изменения при вводе записей в другую, операционную таблицу БД, "Отпуск товаров"

• изменения в НД вносит пользователь. Реализовать пользовательский интерфейс в этом случае можно одним из следующих способов

• пользователь вносит изменения в НД, пользуясь компонентом TDBGrid. Например, для добавления новой записи в НД он нажимает на клавиатуре кнопку Insert, после чего в TDBGrid появляется новая, пустая строка; вводит данные в поля новой строки, запоминает новую запись (перемещая курсор на другую запись) или отказывается от запоминания, нажимая на клавиатуре кнопку Esc;

• пользователь нажимает одну из трех кнопок "Добавить", "Изменить", "Удалить", которые размещены в той же форме, что и компонент TDBGrid, в котором показываются записи набора данных По нажатию кнопки набор данных переводится в соответствующее состояние и вызывается другая форма, в которую пользователь вводит новые значения полей записи и нажимает кнопку "Запомнить изменения " , "Отменить" или "Подтвердить удаление записи". После этого реализуются необходимые действия, то есть выполняются методы Post, Cancel или Delete;

• пользователь нажимает одну из трех кнопок "Добавить", "Изменить", "Удалить", которые размещены в той же форме, что и компонент TDBGrid, в котором показываются записи набора данных. По нажатию кнопки набор данных переводится в соответствующее состояние и в этой же форме активизируется панель, на которой расположены поля текущей записи набора данных. Пользователь вводит новые значения полей записи, после чего подтверждает или отменяет добавление или изменение записи, при удалении записи ему, разумеется, ничего вводить не нужно, а нужно лишь удаление подтвердить или отменить. Рассмотрим реализацию последнего сценария. Пусть имеется ТБД Sklady, записи которой содержат наименования складов Предположим, что эта ТБД будет использоваться в приложении как справочник Запись состоит из единственного поля Sklad. По этому полю построен первичный индекс, т е. данное поле может содержать только уникальное значение.

Поместим в форму компонент DBGndl для показа записей из ТБД Sklady Первоначальную высоту TDBGrid определим равной 257 Ниже DBGndl поместим в форме панель PanelToInput Values и определим ее как "невидимую" (свойство Visible = False) В'данной панели расположим компонент DBEdit1, который ссылается на поле Sklad Соответственно, когда панель невидима, невидимы и компоненты, в ней расположенные

Поместим в форму панель InsertEditDeletePanel и установим ее свойство Visible = True. В панели разместим экранные кнопки для работы с отдельной записью "BcTaBHTb"(InsertButton), "Изменить" (EditButton), "Удалить" (DeleteButton), и кнопку для выхода "Выйти" (ExitButton) с модальным свойством ModalResult = mrOk

Поместим в форме панель PostCancelPanel и сделаем ее невидимой (свойство Visible = False) Разместим в панели экранные кнопки "Запомнить" (PostButton) и "Отменить"(Сапсе1Вийоп) для запоминания сделанных изменений в БД или отказа от запоминания (рис 7.15)

Совместим местоположение панелей PostCancelPanel и InsertEditDeletePanel, сделав их одинаковыми по размеру (рис. 7.16):

Напишем следующие обработчики нажатия экранных кнопок:

// константы для обозначения высоты TDBGrid

const

NORMAL_HEIGHT = 257; // в режиме просмотра записей

UPDATE_HEIGHT = 193; // в режимах добавления и изменения

// Обработчик нажатия кнопки "Включить"

procedure TFormI.InsertButtonClick(Sender: TObject);

begin

// визуализируем панель с кнопками "Запомнить", "Отменить'

PostCancelPanel.Visible := True;

// делаем невидимой панель с кнопками "Включить",

// "Изменить" и т.д.

InsertEditDeletePanel.Visible := False;

// уменьшаем высоту DBGrid

DBGridl.Height := UPDATE_HEIGHT;

// делаем видимой панель для ввода значения в запись

PanelToInputValues.Visible := True;

// переводим НД в режим добавления записи

Tablel.Insert;

// передаем фокус управления на ввод значения в запись

DBEditI.SetFocus;

end;

// Обработчик нажатия кнопки "Изменить"

procedure TFormI.EditButtonClick (Sender: TObject);

begin

// делаем невидимой панель с кнопками "Включить",

// "Изменить" и т.д.

InsertEditDeletePanel.Visible := False;

// уменьшаем высоту DBGrid

DBGridl.Height := UPDATE_HEIGHT;

// делаем видимой панель для ввода значения в запись

PanelToInputValues.Visible := True;

// переводим НД в режим редактирования записи

Tablel.Edit;

// передаем фокус управления на ввод значения в запись

DBEditI.SetFocus;

end;

// Обработчик нажатия кнопки "Удалить"

procedure TFormI.DeleteButtonClick(Sender: TObject);

begin

// удаляем запись, в случае подтверждения пользователем

IF MessageDIg('Подтвердите удаление записи',

mtConfirmation,[mbYes,mbNo],0) = mrYes THEN

Tablel.Delete;

// передаем управление на DBGrid

DBGridI.SetFocus ;

end;

// Обработчик нажатия кнопки "Запомнить"

procedure TFormI.PostButtonClick(Sender: TObject);

begin

// пытаемся запомнить изменения в ТБД; при возникновении //исключения полагаем, что его причиной является дублирова

//ние ключа. В этом случае сообщаем пользователю и отменяем

//внесение изменений в ТБД

TRY

Tablel.Post;

EXCEPT

on EDBEngineError do begin

MessageDIg('Такая запись уже есть',mtlnformation, [mbOk],0) ;

Tablel.Cancel;

end; {on}

END; {try}

// увеличиваем высоту DBGrid

DBGridI.Height := NORMAL_HEIGHT;

// делаем невидимой панель для ввода значения в запись

PanelToInputValues.Visible := False;

// делаем невидимой панель для ввода значения в запись

PostCancelPanel.Visible := False;

// делаем видимой панель с кнопками "Включить", "Изменить" и т.д.

InsertEditDeletePanel.Visible := True;

// передаем управление на DBGrid

DBGridI.SetFocus ;

end;

// Обработчик нажатия кнопки "Отменить"

procedure TFormI.CancelButtonClick(Sender: TObject);

begin

Tablel.Cancel;

// увеличиваем высоту DBGrid

DBGridI.Height := NORMAL_HEIGHT;

// делаем невидимой панель для ввода значения в запись

PostCancelPanel.Visible := False;

// делаем видимой панель с кнопками "Включить", "Изменить" и т.д.

InsertEditDeletePanel.Visible := True;

// делаем невидимой панель для ввода значения в запись

PanelToInputValues.Visible := False;

// передаем управление на DBGrid

DBGridI.SetFocus;

end;

// Обработчик события выхода из формы.

// Если в этот момент НД находится в состоянии,

// отличном от dsBrowse, т.е. в dslnsert, dsEdit,

// он принудительно возвращается в состояние dsBrowse.

procedure TFormI.FormDeactivate(Sender: TObject);

begin

IF Tablel.State 0 dsBrowse THEN Tablel.Cancel;

end;

В режиме просмотра записей (dsBrowse) видим окно, показанное на рис. 7.17

А в режимах добавления новой записи (dslnsert) или редактирования (dsEdit) видим окно, (рис. 7.18)

При нажатии кнопки "Удалить" выдается модальная форма подтверждения удаления (рис. 7.19). Эта форма реализуется функцией Delphi MessageDlg.

7.6. Закладки на записях НД

Подобно тому, как в книге нужную страницу можно заложить закладкой и впоследствии быстро найти эту страницу, в НД аналогичные действия можно осуществить для записи. Для этой цели НД обладает следующими методами:

function GetBookmark: Tbookmark - создает для текущей записи объект-закладку и возвращает ссылку на него;

procedure GotoBookmark(Bookmark : Tbookmark) - перемещает курсор БД на запись, определяемую закладкой-параметром;

procedure FreeBookmark(Bookmark : Tbookmark) - освобождает системные ресурсы закладки Bookmark;

function Bookmark Valid(Bookmark: TBookmark): Boolean; - возвращает True, если закладке Bookmark назначено значение, и False - если не назначено;

function CompareBookmarks(Bookmarkl, Bookmark!: TBookmark): Integer; -сравнивает две закладки, Bookmark 1 и Bookmark!, и возвращает: 0 - если закладки идентичны; 1 - если различаются;

тип TBookmark - указатель на экземпляр типа "закладка".

ЗАМЕЧАНИЕ Метод GotoBookmark возбуждает исключительную ситуацию, если передаваемый параметр не указывает на закладку (т.е. если закладка не создана, а мы пытаемся на нее перейти). Пример

var MyBookmark : TBookMark;

{ заложить закладку }

MyBookmark := Tablel.GetBookmark;

{ перейти на запись, на которой заложена закладка}

IF Tablel. BookmarkValid. (MyBookmark) THEN

Tablel.GotoBookmark(MyBookmark);

{ освободить ресурсы, выделенные для закладки}

IF Tablel. BookmarkValid(MyBookmark) THEN

Tablel.FreeBookmark(MyBookmark);

Закладки используются в первую очередь тогда, когда :

1. Нужно проделать какие-либо действия над НД, изменяющие местоположение курсора БД;

2. Вернуться к той записи, которая была текущей перед выполнением п. 1.

7.7. Поиск записей в наборах данных

Помимо приводимых ниже средств поиска записей в НД, можно воспользоваться методами Find, Go ToKey, FindNearest, Go ToNearest компонента TTable.

7.7.1. Метод Locate

function Locate(const KeyFields: string; const Key Values: Variant;

Options: TLocateOptions): Boolean;

Метод Locate ищет первую запись, удовлетворяющую критерию поиска, и если такая запись найдена, делает ее текущей. В этом случае в качестве результата возвращается True. Если поиск был неуспешен, возвращается False.

Параметры:

Список KeyFields указывает поле или несколько полей, по которым ведется поиск, в виде строкового выражения. В случае нескольких поисковых полей их названия разделяются точкой с запятой.

Критерии поиска задаются в вариантном массиве Key Values так, что i-e значение в Key Values ставится в соответствие i-му полю в KeyFields. В случае поиска по одному полю в Key Values указывается одно значение.

Options позволяет указать необязательные значения режимов поиска:

loCaselnsensilive - поиск ведется без учета высоты букв, т.е. если в Key Values указано 'принтер', а в некоторой записи в данном поле встретилось 'Принтер' или 'ПРИНТЕР', запись считается удовлетворяющей условию поиска;

loPartialKey - запись считается удовлетворяющей условию поиска, если она содержит часть поискового контекста; например, удовлетворяющими контексту 'Ма' будут признаны записи с значениями в искомом поле "Машин ', 'Макаров' и т.д.

Locate отличается от методов FindKey, FindNearest, GoToKey, Go ToNearest (компонент TTable) следующим:

FindKey, FindNearest, GoToKey, Go ToNearest производят поиск только по полям, входящим в состав текущего индекса TTable; в случае, когда условию поиска удовлетворяет несколько записей, текущей станет логически самая первая из них (в порядке сортировки записей в НД, определяемом текущим индексом);

Locate производит поиск по любому полю; поле или поля, по которым производится поиск, могут не только не входить в текущий индекс, но и не быть индексными вообще.

В случае, если поля поиска входят в какой-либо индекс, Locate использует этот индекс при поиске. Если искомые поля входят в несколько индексов, трудно сказать, какой из них будет использован. Соответственно, трудно предсказать, какая запись из множества записей, удовлетворяющих критерию поиска, будет сделана текущей - особенно в случае, если поиск ведется не по текущему индексу.

При поиске по полям, не входящим ни в один индекс, применяются фильтры BDE.

Пример. Пусть имеется ТБД "Сотрудники кафедры" с целочисленным TabNum (табельный номер) и строковыми полями FIO (ФИО), Doljnos) (Должность), UchStepen (Ученая степень).

Пусть ТБД имеет индексы по полям: 'TabNum' , 'FIO', 'Doljnost;FIO'.

Пример А. Осуществим поиск по полям 'Doljnost; UchStepen' (индексное и неиндексное) при различных текущих индексах в НД. Поисковый контекст -['доцент', 'кхн'] при режиме частичного совпадения значений:

procedure TFormX.LocateButtonClick(Sender: TObject);

begin

Tablel.Locate ( 'Doljnost;UchStepen',

VarArrayOf(['доцент','кхн']),[loPartialKey]) ;

end;

Результаты поиска показаны на рис.7.20 - 7.22. Как видно из рисунков, при различных текущих индексах в момент выполнения поиска, результаты поиска также могут быть различными.

Пример Б Осуществим поиск по полю 'FIO' ( входит в два индекса) при различных текущих индексах в НД Поисковый контекст - ['Ма'] при режиме частичного совпадения значении

procedure TFormX.LocateButtonClick(Sender: TObject);

begin

Tablel.Locate('FIO','Ma', [ioPartialKey]) ;

end;

Этому критерию соответствуют записи с FIO = 'Манишкина А А ', 'Мануйлова В А','Маслаченко В Ф ', ' Массалитин В Ф ' (см рис 7 23 - 7 25) Как видим, результат поиска при различных текущих индексах одинаков

Пример В Пусть заранее неизвестно, по какому полю необходимо производить поиск Тогда поместим в форму компонент RadioGroup1, в котором перечислим поля поиска, и компонент Edit1 для ввода условий поиска (см рис 7.26)

Напишем такой обработчик

procedure TFormX.ButtonlClick(Sender: TObject) ;

var Pole : Shortstring;

begin

CASE RadioGroupl.Itemlndex OF

0 : Pole := 'TabNum' ;

1 : Pole := 'FIO';

2 : Pole := 'Doljnost;

3 : Pole := 'UchStepen';

END;

IF not Tablel.Locate(Pole,Editl.Text,[loCaselnsensitive, loPartialKey]) THEN ShowMessage('Запись не найдена');

end;

Преимущество показанного способа в том, что мы вместо выполнения нескольких Locate (для поиска по каждому полю) выполняем один метод Locate независимо от поля, по которому производится поиск

7.7.2. Использование методов FindFirst, FindLast, FindNext, FindPrior

Известно, что набор данных может быть отфильтрован с использованием свойства Filtered Условие фильтрации задается свойством Filter или описывается в обработчике события OnFliter Re cord Свойство Filtered указывает, выполнять ли фильтрацию (значение True) или нет (значение False) В этом случае в НД показываются все записи, а не только удовлетворяющие условию фильтрации

Для НД, в котором определены условия фильтрации, но сама фильтрация в текущий момент не включена, Delphi предоставляет интересную возможность Она заключается в том, что в неотфильтрованном в данный момент НД можно обеспечить навигацию только между теми записями, которые удовлетворяют условию фильтрации (оно в текущий момент, когда свойство Filtered = False, не действует)

Для этой цели используются методы FindFirst, FindLast. FindNext, FindPrior

Условие фильтрации можно сделать совпадающим с условием поиска, указанным в параметре./^ Values метода Locate При этом поиск с помощью указанных методов имеет довольно большое преимущество перед поиском с помощью Locate если в Locate можно указывать только значения, то в условии фильтрации можно указывать логические условия, например

Accept := (DataSet['Doljnosf] = 'доцент') AND (DataSet['TabNum'] > 150000);

т е. включать в НД только записи о сотрудниках-доцентах, и притом не всех, а только тех из них, у которых табельный номер больше 150000

В случае, если искомая запись найдена, данные методы возвращают True, в противном случае - False. Аналогичный результат возвращает свойство Found

function FindFirst: Boolean; - переходит на первую запись, удовлетворяющую фильтру;

function FindLast: Boolean; - переходит на последнюю запись, удовлетворяющую фильтру;

function FindNext: Boolean; - переходит на следующую запись, удовлетворяющую фильтру;

function FindPrior: Boolean; - переходит на предыдущую запись, удовлетворяющую фильтру;

property Found: Boolean; - возвращает True, если последнее обращение к одному из методов FindFirst, FindLast, FindNext, FindPrior привело к нахождению нужной записи.

Пример. Предоставить пользователю возможность перемещаться на первую, последнюю, следующую, предыдущую запись, удовлетворяющую условию "Содержимое Edit1 входит как часть ФИО сотрудника". Заметим, что свойство Table1.Filtered = False, т.е. хотя в обработчике события Table1.OnFilterRecord и указано условие фильтрации, в НД показываются все записи, и он остается в неотфильтрованном состоянии (см. рис. 7.27 и 7.28)

условие фильтрации

procedure TFormX.TablelFilterRecord(DataSet: TDataSet;

var Accept: Boolean);

begin

Accept := POS(Editl.Text,DataSet['FIO']) > 0;

end;

// нажата кнопка "Первая"

procedure TFormX.FindFirstButtonClick(Sender: T0b;ect) ;

begin

Labell .Caption := ";

IF not Tablel.FindFirst THEN

Labell.Caption := 'Нет такой записи';

end;

// нажата кнопка "Последняя"

procedure TFormX . FmdLastButtonClick (Sender : TObject) ;

begin

Labell.Caption := ";

IF not Tablel.FindLast THEN

Labell.Caption := 'Нет такой записи';

end;

// нажата кнопка "Следующая"

procedure TFormX.FindNextButtonClick(Sender: TObject);

begin

Labell.Caption := ";

IF not Tablel.FindNext THEN

Labell.Caption := 'Нет такой записи';

end;

// нажата кнопка "Предыдущая"

procedure TFormX.FindPriorButtonClick(Sender: TOb^ect) ;

begin

Labell.Caption := ";

IF not Tabiel.FindPrior THEN

Labell.Caption := 'Нет такой записи';

end;

 

Заметим, что поскольку фильтрация записей с использованием события OnFilter Record или (и) свойства Filter может применяться только на небольших объемах записей (из-за того, что при этом используется последовательный метод доступа к записям в ТБД), аналогичные ограничения накладываются и на поиск записей с использованием методов FindFirst, FindLast, FindNext, FindPrior.

7.7.3. Метод Lookup

function Lookup(const KeyFields: string; const Key Values: Variant;

const ResultFields: string): Variant;

Метод Lookup находит запись, удовлетворяющую условию, но не делает ее текущей, а возвращает значения некоторых полей этой записи. Тип результата - Variant или вариантный массив. Независимо от успеха поиска записи, указатель текущей записи в НД не изменяется.

Lookup осуществляет поиск только на точное соответствие критерия поиска и значения полей записи. Такой режим, как loPartialKey метода Locate (поиск по частичному соответствию значений), отсутствует.

Параметры:

В KeyFields указывается список полей, по которым необходимо осуществить поиск. При наличии в этом списке более чем одного поля, соседние поля разделяются точкой с запятой.

Key Values указывает поисковые значения полей, список которых содержится в KeyFields. Если имеется несколько поисковых полей каждому i-му полю в списке KeyFields ставится в соответствие i-oe значение в списке Key Values. При наличии одного поля, его поисковое значение можно указывать в качестве Key Values непосредственно; в случае нескольких полей - их необходимо приводить к типу вариантного массива при помощи Var-Array'Of.

В качестве поисковых полей можно указывать поля как входящие в какой-либо индекс, так и не входящие в него; тип текущего индекса не имеет значения. Если поисковые поля входят в какие-либо индексы, их использование производится автоматически; в противном случае используются фильтры BDE.

Если запись в результате поиска не найдена, метод Lookup возвращает Null, что выявляется при помощи предложения

IF VarType(LookupResults) = varNull THEN ...

В противном случае Lookup возвращает из этой записи значения полей, список которых указан в ResultFields. При этом размерность результата зависит от того, сколько результирующих полей указано в ResultFields'.

указано одно поле - результатом будет значение соответствующего типа или Null, если поле в найденной записи содержит пустое значение;

указано несколько полей - результатом будет вариантный массив, число элементов в котором меньше или равно числу результирующих полей;

меньше потому, что некоторые поля найденной записи могут содержать пустые значения. Рассмотрим несколько вариантов.

Пример А. Одно результирующее поле (результат - значение типа Variant) Будем осуществлять поиск в ТБД "Сотрудники" по полю 'FIO'. Поисковое значение будем вводить в Edit I. В качестве результата будем выдавать значение поля "UchStepen' (ученая степень) найденной записи.

procedure TFormX.LookuplButtonClick(Sender: TObject);

var

LookupResults : Variant; // результат

begin

// осуществить поиск

LookupResults := Tablel.Lookup('FIO', Editl.Text, 'UchStepen') ;

Label 1.Caption := ";

// содержит ли результат пустое значение или Null?

CASE VarType(LookupResults) OF

varEmpt : Labell.Caption := 'Пустой результат';

varNull : Labell.Caption := 'Запись не найдена';

ELSE

// нет, результат содержит какое-то значение

Labell.Caption := LookupResults;

END; //case

end;

Заметим,что в присваивании

Labell.Caption := LookupResults;

имеет место приведение вариантного типа к строковому. Более подробно о приведении вариантных типов см. описание вариантного типа в документации и встроенной системе помощи Delphi.

Пример Б. Несколько результирующих полей (результат - вариантный массив)

Некоторые сведения по использованию вариантного массива:

Если переменная типа Variant является вариантным массивом, функция VarIsArray(LookupResults) возвращает True.

При работе с переменным числом возвращаемых полей, в конкретном случае верхнюю и нижнюю границы массива LookupResults можно определить при помощи функций VarArrayLowBound(LookupResults, 1) и VarArrayHigh-Bound( LookupResults, 1).

Тип i-го элемента вариантного массива можно определить как VarType (LookupResults [ i]).

Будем осуществлять поиск в ТБД "Сотрудники" по полю 'FIO\ Поисковое значение будем вводить в Editi. В качестве результата будем выдавать значения полей "TabNum,DolJnost,UchStepen' найденной записи (табельный номер, должность, ученая степень).

procedure TFormX.LookupButtonClick(Sender: TObject) ;

var

LookupResults : Variant; // результат

begin

// осуществить поиск

LookupResults := Tablel.Lookup('FIO', Editl.Text, '' TabNum;Doljnost;UchStepen') ;

// будем показывать значения результирующих полей в TLabel

Labell.Caption := ";

Label2.Caption := ";

Label3.Caption := ";

// результат - вариантный массив ?

IF VarIsArray(LookupResults) THEN

begin

Labell.Caption := LookupResults[0];

IF LookupResults[1] 0 Null THEN

Label2.Caption := LookupResults [1];

IF LookupResults[2] о Null THEN

Label3.Caption := LookupResults [2];

end // then

ELSE

// результат - не вариантный массив, а единичное значение

CASE VarType(LookupResults) OF

varEmpty : Labell.Caption := 'Пустой результат';

varNull : Labell.Caption := 'Запись не найдена';

END; //case

end;

Если запись не найдена, VarType (LookupResults) возвращает значение varNull; если поиск по какой-либо причине не был произведен, VarType (LookupResults) возвращает значение varEmpty. Если какое-либо из полей, чьи значения возвращаются в результате поиска в вариантном массиве, содержит пустое значение, соответствующий элемент вариантного массива также будет содержать пустое значение (Null). В этом случае обращение к нему возбудит исключительную ситуацию, поэтому нужна предварительная проверка.

7.8. Фильтрация записей в наборах данных

Помимо описываемых ниже средств, для фильтрации данных могут использоваться: методы SetRange или ApplyRange (и сопутствующие им методы) - в компоненте Table; секция WHERE оператора SELECT языка SQL - в компоненте TQuery.

7.8.1. Свойство Filtered

property Filtered: Boolean;

Свойство Filtered, установленное в True, инициирует фильтрацию, условие которой записано или в обработчике события OnFilterRecord, или содержится как строковое значение в свойстве Filter. Если установлены разные условия фильтрации и в событии OnFilterRecord, и в свойстве Filter, выполняются оба.

Например, если в НД одновременно установлены фильтры

Tablel.Filter = ' [Doljnost] = 'профессор''';

procedure TFormX.TablelFilterRecord(DataSet: TDataSet;

var Accept: Boolean);

begin

Accept := DataSet['UchStepen'] = 'дтн';

end;

то установка Tablel Filtered в True приведет к двум фильтрациям; в результирующем наборе данных будут показаны только записи, у которых поле Doljnost содержит значение 'профессор' и поле UchStepen содержит значение 'дтн'.

Установка Filtered^ False приведет к отмене фильтрации, условия которой указаны в событии OnFilterRecord или (и) свойстве Filter. При этом фильтрация,

наложенная на НД методом SetRange или ApplyRange и ему сопутствующими методами, не нарушается

Пример Пусть ТБД "Сотрудники" подвергается фильтрации

procedure TFormI.Table1FilterRecord(DataSet: TDataSet;

var Accept: Boolean);

begin

Accept := DataSet['UchStepen'] = 'доцент';

end;

и по нажатию кнопки "SetRange" показываются только записи, у которых табельный номер больше 150000

procedure TFormI.SetRangeClick(Sender: TObject) ;

begin

Tablel.SetRange ( [150000],[900000]) ;

end;

Нажатие клавиши "CancelRange" снимает фильтрацию по табельному номеру (рис 7 29 - 7 31)

procedure TFormI.CancelRangeClick(Sender: TOb^ect);

begin

Tablel.CancelRange;

end;

Последовательность установки фильтров произвольна - SetRange может применяться после Filtered = True, и наоборот

Отмена одного из этих условий фильтрации не приводит к отмене другого способа фильтрации (рис 7 32, 7 33)

или

 

 

7.8.2. Событие OnFilterRecord

property OnFilterRecord: TFilterRecordEvent;

Событие OnFilterRecord возникает, когда свойство Filtered устанавливается в Tine Обработчик события OnFilterRecord имеет два параметра имя фильтруемого набора данных и var Accept, указывающий условия фильтрации записей в НД В отфильтрованный ИД включаются только те записи, для которых параметр Accept имеет значение True

В условие фильтрации могут входить любые поля НД, в том числе не входящие в текущий индекс, а также не входящие ни в один индекс Возможность фильтрации НД по неиндексным полям, а также полям, не входящим в текущий индекс, выгодно отличает способ фильтрации с использованием события OnFilterRecord и свойства Filteredот способов фильтрации с использованием методов Set Range, Apply Range и им сопутствующих методов (компонент TTable) Последние, как будет показано в разделе, посвященном компоненту TTable, позволяют производить фильтрацию НД только по индексным полям, входящим к тому же в состав индекса, текущего на момент фильтрации Кроме этого, второй способ часто не позволяет реализовывать сложные логические конструкции при указании условий фильтрации

Однако следует помнить о том, что при указании условий фильтрации НД в обработчике OnFilterRecord, в нем последовательно перебираются все записи ТБД при анализе их на предмет соответствия условию фильтрации, в то время как методы SetRange, ApplyRange и им сопутствующие методы используют индексно-последовательный метод доступа, т е работают с частью записей в физической ТБД Это делает использование OnFilterRecord предпочтительным для небольших объемов записей и сильно ограничивает применение данного способа фильтрации при больших объемах данных

Всякий раз, когда приложение обрабатывает событие OnFilterRecord, НД переводится из состояния dsBrowse в состояние dsFilter Это предотвращает модификацию НД во время фильтрации После завершения текущего вызова обработчика события OnFilterRecord, НД переводится в состояние dsBro\\se

Пример Отфильтровать ТБД "Сотрудники" согласно условию "Показать всех доцентов"

procedure TFormI.TablelFilterRecord(DataSet: TDataSet;

var Accept: Boolean);

begin

Accept := DataSet['Doljnost] = 'доцент';

end;

Прцмер Отфильтровать ТБД "Сотрудники' по условию 'Показать всех сотрудников с табельным номером, вводимым в Editi, и с вхождением в ФИО символов, вводимых пользователем в Edit2"

procedure TFormI.TablelFilterRecord(DataSet: TDataSet;

var Accept: Boolean);

begin

Accept :- (DataSet['TabNum'] > Editi.Text))

AND (Pos(Edit2.Text,DataSet['FIO']) > 0);

end;

Методы FindFirst, FindLast, FindNext, FindPrior (см. выше подраздел "Поиск записей в наборах данных") также используют свойство OnFilterRecord, когда выполняют навигацию по НД.

7.8.3. Свойство Filter

property Filter: string;

Свойство Filter позволяет указать условия фильтрации. В этом случае НД будет отфильтрован, как только его свойство Filtered станет равным True. Синтаксис похож на синтаксис предложения WHERE SQL-оператора SELECT с тем исключением, что: имена переменных программы указывать нельзя, можно указывать имена полей и литералы (явно заданные значения).

Можно применять операторы отношения:

< Меньше чем; > Больше чем; >= Больше или равно; <= Меньше или равно; = Равно; <> Не равно

а также использовать логические операторы AND, NOT и OR:

([Doljnost] = 'доцент') AND ([TabNum] > 300000)

Строку фильтрации можно ввести во время выполнения (рис. 7.34, 7.35):

//когда проставляется галка в поле компонента CheckBoxl (то есть

//когда CheckBoxl.Checked =True), пользователь включает

//фильтрацию; когда пользователь снимает отметку (то есть когда

// CheckBoxl.Checked =True), пользователь выключает фильтрацию

procedure TFormI.CheckBoxIClick(Sender: TObject);

begin

Table1.Filter := Editl.Text;

Table1.Filtered := CheckBoxl.Checked;

end;

Однако при этом нужно следить, чтобы введенная строка соответствовала требованиям, предъявляемым к синтаксису строки Filter.

Другим способом мог бы быть обработчик, считывающий значения фильтрации и преобразующий их к формату строки Filter. На рис. 7.36 и 7.37 показана форма, где значения, по которым осуществляется фильтрация, вводятся в поля компонентов TEdit1. После этого приложение автоматически формирует строку условия фильтрации и заносит ее в свойство Filter. Сформированная строка условия фильтрации для наглядности показывается в форме приложения в компоненте Label3.

7.8.4. Свойство FilterOptions

property FilterOptions: TFilterOptions;

TFilterOption = (foCaseInsensitive, foNoPartialCompare);

Свойство FilterOptions позволяет установить режимы фильтрации с использованием свойства Filter. По умолчанию FilterOptions = [ ];

foCaseInsensitive - Фильтрация производится без учета разницы в высоте букв;

foNoPartialCompare - поиск производится на точное соответствие. В противном случае, при фильтре FIO = 'Ма' в отфильтрованный НД будут включены записи, у которых в поле FIO частично входит "Ма". например (если не используется опция foCaseInsensitive), 'Мануйлова' и 'Комарова'.

7.8.5. Навигация в неотфильтрованном НД между записями, удовлетворяющими фильтру

Методы FindFirst, FindLast, FindNext, FindPrior позволяют перемещаться в неотфильтрованном НД (у которого Filtered = False) между записями, удовлетворяющими условию фильтрации. Условие фильтрации задается событием OnFilterRecord или (и) свойством Filter. Действие данных методов таково: они кратковременно переводят НД в отфильтрованное состояние (Filtered = True) без визуализации этой фильтрации в TDBGrid или другом подобном компоненте, находят соответствующую запись и переводят НД в неотфильтрованное состояние (Filtered = False).

Если искомая запись найдена, данные методы возвращают True, в противном случае - False. Аналогичный результат возвращает свойство Found.

Более подробно указанные методы рассмотрены в подразделе "Поиск записей в наборах данных" (см. п.7.7).

7.9. Получение информации о полях

7.9.1. Использование компонента TFieldDefs

Компонент TFieldDefs содержит информацию о полях, объявленных в составе ТБД, ассоциированной с данным НД.

Для НД типа TTable компонент TFieldDefs содержит информацию обо всех полях, объявленных в структуре ТБД, ассоциированной с данным TTable. He следует путать это свойство с свойством Fields, которое содержит информацию о всех компонентах типа TField, объявленных для данного набора данных с использованием редактора полей.

Для НД типа TQuery компонент TFieldDefs содержит информацию обо всех полях, объявленных в качестве возвращаемых полей в операторе SELECT. Подробнее см. замечание, помещенное в конце данного подраздела. Свойство набора данных

property FieldDefs: TFieldDefs;

в качестве результата возвращает указатель на объект типа TFieldDefs данного НД и, таким образом, с его помощью можно использовать свойства и методы

компонента TFieldDefs для определения числа и характеристик полей, объявленных в таблице базы данных, ассоциированной с данным НД. Рассмотрим свойства и методы компонента TFieldDefs.

Свойства компонента TFieldDefs

• property Count: Integer; Возвращает количество компонентов типа TIndexDef, каждый из которых содержит информацию о конкретном поле в составе ТБД.

• property Items[Index: Integer]: TFieldDef; Данное свойство является набором объектов типа TFieldDef, каждый из которых содержит информацию о конкретном поле, объявленном в составе ТБД. Доступ к конкретному объекту осуществляется через указание Items[Index], где Index лежит в диапазоне Q..Count-\.

Объект типа TFiezldDef обладает следующими свойствами:

• property DataType: TFieldType; Свойство DataType возвращает тип поля как значение из перечислимого типа TFieldType:

TFieldType = (ftString, ftSmallint, ftlnteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftBytes, ftVarBytes, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftParadoxOle, ftDBaseOle, ftTypedBinary);

• property FieldNo: Integer; Свойство FieldNo возвращает физический номер поля, использующийся Borland Database Engine (BDE) для доступа к полю.

• property Name: string; Свойство Name возвращает физическое имя поля в ТБД.

• property Required: Boolean; Данное свойство возвращает True, если поле требует обязательного заполнения каким-либо значением, и False в противном случае.

• property Size: Integer; Данное свойство возвращает размер поля. Он важен для полей типов: ftString, ftBCD, ftBytes, ftVarBytes, ftBlob, ftMemo or ftGraphic. Для полей всех остальных типов размер поля определяется его типом. Для полей BCD возвращается число знаков после десятичной точки.

Пример. Считать сведения о полях ТБД, ассоциированной с Table 1, и поместить в ListBox1 информацию об имени каждого поля, его типе, размере и значении его свойства Required (результат работы приводимого кода на рис.7.38):

var i : Integer;

TipPolja : String; // тип поля

Tmp : String; // результат форматирования

TmpReq : String[3];//рабочая переменная

begin

ListBoxl.Clear;

Tablel.FieldDefs.Update;

// считываем поля и заносим в ListBoxl сведения об

//их имени, типе, размере и свойстве Required

FOR i := О ТО Tablel.FieldDefs.Count - 1 do begin

CASE Tablel.FieldDefs.Items[i].DataType OF

ftString TipPolja = 'String';

ftSmallint TipPolja = 'Smallint' ;

ftlnteger TipPolja == 'Integer';

ftWord TipPolja = 'Word';

ftBoolean TipPolja = 'Boolean';

ftFloat TipPolja = 'Float';

ftCurrency TipPolja = 'Currency' ;

ftBCD TipPol;a = 'BCD';

ftDate TipPolja = 'Date';

ftTime TipPolja = 'Time';

ftDateTime TipPolja = 'DateTime';

ftBytes TipPolja = 'Bytes' ;

ftVarBytes TipPolja = 'VarBytes' ;

ftAutoInc TipPolja = 'AutoInc';

ftBlob TipPolja = 'Blob';

ftMemo TipPolja = 'Memo';

ftGraphic TipPolja = 'Graphic';

ftFmtMemo TipPolja = 'FmtMemo' ;

ftParadoxOle TipPolja = 'ParadoxOle';

ftDBaseOle TipPolja = 'DBaseOle';

ftTypedBinary TipPolja = 'TypedBinary';

END;

IF Tablel.FieldDefs.Items[i].Required THEN

TmpReq := 'Req'

ELSE

TmpReq := ' - ';

Tmp := Format('%-16s %-10s %-5s %4d', [Tablel.FieldDefs.Items[i].Name, TipPol]a, TmpReq, Tablel.FieldDefs.Items[i].Size]) ;

ListBoxl.Items.Add(Tmp);

END;

Методы компонента TFieldDefs

procedure Add(const Name: string; DataType: TFieldType; Size: Word; Required: Boolean);

Метод Add добавляет в список Items новый элемент- поле.

Параметр Name определяет имя нового поля.

Параметр DataType определяет тип поля как одно из значений перечислимого типа TFieldType (см. выше описание свойства TTable. TFieldDefs.Items [Index].DataType).

Параметр Si:e указывает размер поля или 0 в случае, когда размер поля явно определяется его типом (например, ftlnteger определяет целочисленное поле длиной в слово).

Параметр Required определяет, должно ли поле в обязательном порядке содержать какое-либо значение, или не должно.

procedure Clear; Метод Clear очищает FieldDefs. Это необходимо, например, при создании новой ТБД (см. метод TTable. CreateTable.

function Find(const Name: string): TFieldDef; Метод Find ищет поле по его имени, определяемому параметром Name. В случае успеха метод возвращает указатель на объект TIndexDefs. Items.

function Index0f(const Name: string): Integer; Метод Index Of ищет поле по его имени и возвращает индекс объекта TIndexDefs списке Items. У найденного элемента значение свойства Name совпадает с параметром Name метода.

procedure Update; Метод Update обновляет содержимое свойства TFieldDef текущей информацией о полях в составе ТБД. Он также позволяет заносить в TFieldDef информацию о полях неоткрытого НД.

ЗАМЕЧАНИЕ. Для НД типа TQuery в свойстве FieldDefs будет содержаться информация только о тех полях, которые представлены в списке после оператора SELECT:

SELECT P.NN, P.DatePrih, P.Tovar

FROM prihod P

WHERE ...

Тогда свойство FieldDefs будет содержать информацию только о полях NN, DatePrih, Tovar таблицы Prihod.

Это правило действует и для случая двух и более таблиц БД, участвующих в запросе:

SELECT P.NN, P.DatePrih, P.Tovar, P.Kolvo, K.KursUSD

FROM prihod P, KursUSD К

WHERE K.Datel= P.DatePrih

В случае использования в качестве полей результирующего запроса агрегированных функций типа SUM, а также результатов выражений, имя вычисляемого или агрегированного поля берется по его псевдониму, следующему после ключевого слова as:

SELECT P.NN, P.DatePrih, P.Tovar, P.Kolvo, K.KursUSD, P.KolVo * K.KursUSD as Zena

FROM prihod P, KursUSD К

WHERE K.Datel= P.DatePrih

В данном случае вычисляемое поле P.KolVo * K.KursUSD имеет в результирующем НД имя ' Zena '.

Если псеводним вычисляемому или агрегированному полю не присвоен, его имя берется как копия арифметического выражения, по которому значение этого поля вычисляется. Например:

SELECT P.NN, P.DatePrih, P.Tovar, P.Kolvo, K.KursUSD,

P.KolVo * K.KursUSD

FROM prihod P, KursUSD К

WHERE K.Datel= P.DatePrih

Имя вычисляемого по выражению P.Kolvo * K.KursUSD поля будет P.KolVo * K.KursUSD '.

 

7.9.2. Использование свойств FieldCount и Fields

property FieldCount: Integer;

Свойство FieldCount возвращает число компонентов TField, определенных в редакторе полей для данного НД. В общем случае число TField не равно числу полей, физически объявленных в ТБД, ассоциированной с данным TTable, поскольку из объявленных полей в качестве TField могут быть добавлены не все, плюс к тому могут быть объявлены, например, вычисляемые поля.

В том случае, если с использованием редактора полей ни одно поле из физически объявленных в структуре ТБД не было добавлено в форму в качестве TField, FieldCount будет возвращать значение, совпадающее с числом физически объявленных в ТБД полей.

Для компонента TQuery всегда будет возвращаться число полей, объявленных в качестве полей возвращаемого результирующего НД в операторе SELECT:

SELECT F.polel, F.pole2, S.pole99, ...

FROM first F, second S, ...

При этом не важно, сколько имеется ТБД-источников для SQL-запроса и к какому из них принадлежат возвращаемые поля, поскольку они считаются принадлежащими набору данных, возвращаемому в результате выполнения оператора SELECT, и ни с чем иным в данном контексте не ассоциируются.

property Fields[Index: Integer]: TField; Свойство Fields есть набор компонентов TField, определенных для НД. К отдельному полю можно обратиться, указав Fields /Index J, где Index лежит в диапазоне O..Count-1.

Пример. Пусть в редакторе полей для Table 1 определена группа полей (компонентов TField). Необходимо записать в ListBox2 имена физических полей и для каждого из них - имя компонента TField, ассоциированного с полем:

ListBox2.Clear;

FOR i := 0 ТО Table1.FieldCount - 1 do

ListBox2.Items.Add(Format('%-16s %-10s', [Tablel.Fields[i].FieldName, Tablel.Fields[i].Name])) ;

Как можно видеть из рис.7.39, использование свойств FieldDefs и Fields имеет разную природу: если первое выдает информацию о физически объявленных полях в ТБД, второе выдает сведения о логических полях НД, т.е. полях, добавленных в коллекцию объектов типа TField для этого НД:

Рис 7.39 Информация о компонентах TField

Заметим, что поле VychislPole является вычисляемым и физически в структуре ТБД отсутствует.

7.9.3. Свойства DefaultFields, CacheBlobs, метод ClearFields

Свойство property DefaultFields Boolean; содержит True, если для НД используются динамически создаваемые поля (из структуры записи таблицы БД), и False, если для НД определены постоянные поля (компоненты TField) в редакторе полей.

procedure ClearFields; Метод очищает содержимое полей текущей записи набора данных. Если НД не находится в режиме вставки новой записи или редактирования, возбуждается исключение. В случае успешного выполнения вызывается обработчик события OnDataChange для компонента TDataSource, связанного с НД.

property CacheBlobs: Boolean;

Определяет, выделяется ли в памяти буфер для хранения содержимого blob-поля текущей записи НД. Если свойство установлено в True (значение по умолчанию), буфер выделяется, если False - нет. Буфер необходим, если содержимое blob-поля (например, мемо-поля) показывается в форме для текущей записи НД и должно быстро обновляться при переходе на новую запись.

7.9.4. Способы обращения к полям набора данных

function FieldByName(const FieldName: string): TField;

Позволяет обращаться к полю, объявленному в структуре таблицы (TTable), или к полю, перечисленному в числе прочих в структуре набора данных, возвращаемого оператором SELECT (компонент TQuery). Например:

Table1.FieldByName('FIO').Value := 'Иванов И.И.';

Аналогичным целям служит свойство property FieIdValues[const FieldName: string]: Variant;

Оно позволяет обращаться к полю по его имени FieldName, например:

Table1.FieldValues['FIO'] := 'Иванов И.И.';

НД данных по умолчанию, его имя при обращении к полю можно опускать

Table1['FIO'] := 'Иванов И.И.';

property Fields(Index: Integer]: TField; Позволяет обращаться к полю НД по его индексу Например:

Table1.Fields[2].Value := 'Иванов И.И.';

К компоненту TField, созданному с помощью редактора полей, можно также обращаться через его имя. По умолчанию оно формируется из имени набора данных и названия поля:

Table1FIO.Value := 'Иванов И.И.';

7.10. Блокировка таблиц в многопользовательском режиме

При работе с таблицами локальных СУБД (Paradox dBase) в многопользовательском режиме может возникнуть ситуация, когда на период внесения изменений в какую-либо таблицу БД одним пользователем следует блокировать внесение изменений в таблицу со стороны других пользователей. Более жестким ограничением может служить запрет на просмотр содержимого таблицы со стороны других пользователей.

Запрет внесения изменений или просмотра другими пользователями НД, ассоциированного с данной таблицей, достигается с помощью метода procedure LockTable(LockType: TLockType); где параметр LockType определяет вид запрета:

ItReadLock - запретить чтение;

ItWriteLock - запретить запись

Можно запретить и чтение, и запись данных, но для этого нужно вызвать метод LockTable дважды.

Попытка внесения изменений в НД, ассоциированный с таблицей БД, для которой запрещена запись данных, равно как и попытка чтения из запрещенной для чтения таблицы , приводят к возбуждению исключения EDBEngmeError с сообщением 'Table is locked .'.

После того, как необходимость запрета пропадет, он должен быть отменен, что осуществляется методом procedure UnlockTable(LockType: TLockType); где параметр LockType указывает тип снимаемого запрета. Пример. Если таблица БД запрещена для записи в нее данных, в приложениях других пользователей исключение возбуждается при попытке перевести НД, ассоциированный с таблицей, в режим dsEdit (методом Edit), или удалить запись (метод Delete}. Будем анализировать успешность выполнения метода Edit в обработчике события возникновения ошибки при попытке перевести НД в режим редактирования Для этого можно написать такой обработчик события OnEditError.

procedure TForm1. Table1EditError(DataSet: TDataSet; E: EDatabaseError;

var Action: TDataAction);

begin

ShowMessage ('Таблица блокирована на запись другим пользователем') ;

Action := daAbort;

end;

ЗАМЕЧАНИЕ. В архитектуре "клиент-сервер" не возникает потребности в реализации программных блокировок. Возможность одного пользователя вносить изменения в данные, уже корректируемые другим пользователем, определяется уровнем изоляции транзакций в приложении (свойство Translsolation компонента TDatabase) и уровнем изоляции транзакций сервера, а также механизмом блокировок сервера. Обычно блокировка накладывается на запись, измененную в рамках незавершенной транзакции. Однако, например, SQL-сервер Borland InterBase не блокирует чтение из записей, которые изменяются другим пользователем в рамках еще не завершенной транзакции В этом случае тот пользователь, чье приложение читает записи, видит записи в их последнем подтвержденном состоянии.

7.11. Синхронизация содержимого наборов данных

Записи НД (компонент TTable, TQuery) размещаются в локальной копии на компьютере, на котором выполняется приложение Обновление локальной копии данных происходит в случае выполнения из данного приложения операции модификации НД (добавления, изменения или удаления записи, т е выполнения методов Post или Delete) Однако возникают случаи, когда содержимое локальной копии данных на конкретном компьютере (то есть, попросту говоря, содержимое НД) должно быть обновлено из физической таблицы БД (или синхронизировано с физической таблицей БД) Это достигается путем выполнения метода procedure Refresh;

При этом может встретиться два основных способа применения этого метода. Они рассматриваются ниже.

7.11.1. Синхронизация содержимого наборов данных в одном приложении

Как известно, в приложении может существовать несколько НД, ассоциированных с одной и той же таблицей БД. Например, это может быть компонент Table1, расположенный в модуле данных (Data Module) приложения, и компонент Table2, расположенный в форме и выполняющий там специфические функции, отличные от функций компонента Table 1. Тогда, если эти компоненты активны во время выполнения приложения, нужно обновлять содержимое одного набора данных в случае обновления другого.

Если, например, изменяется TDataModulel. Table 1, то для синхронизации изменения с содержимым TForm1 .Table2 следует написать такие обработчики событий:

procedure TDataModulel.TablelAfterDelete(DataSet: TDataSet);

begin

TFormI.Table2.Refresh;

end;

procedure TDataModulel.Table1AfterPost(DataSet: TDataSet);

begin

TFormI.Table2.Refresh; end;

7.11.2. Синхронизация содержимого наборов данных в разных приложениях

При многопользовательском режиме доступа к БД из нескольких копий одного и того же приложения бывает необходимо обеспечить такую функциональность, чтобы каждый пользователь видел подтвержденные изменения, внесенные другими пользователями.

В клиентских приложениях, работающих в рамках архитектуры "клиент-сервер", подобная функциональность обеспечивается автоматически при уровне изоляции транзакций (Read Committed) и при условии использования компонента TTable. Компонент TQuery, выполнив запрос на чтение к удаленной БД, показывает записи НД в неизменном виде, независимо от того, изменялось ли после запроса содержимое таблицы БД. Для обновления информации в НД, реализуемом при помощи TQuery, приходится закрывать и повторно открывать компонент TQuery, т.е. повторно выполнять запрос к удаленной БД. При уровне изоляции транзакций Repeatable read пользователь в рамках транзакции видит данные только в том состоянии, в котором они находились на момент старта транзакции.

В приложениях, работающих в архитектуре "файл-сервер" (с использованием таблиц локальных СУБД Paradox, dBase), не происходит обновления в наборе данных (находящегося в режиме dsBrowse) в приложении одного пользователя, после внесения изменения в эту таблицу БД другим пользователем в своем

приложении. Отображение изменений производится лишь после выполнения данным пользователем метода Post или Delete. Если НД находится в состоянии dsBrowse, его обновление можно реализовать периодически, например, с помощью таймера:

procedure TFormI.TimerlTimer(Sender: TObject) ;

begin

IF Table1.State = dsBrowse THEN Table1.Refresh; end;

или, если нужно обновлять все НД приложения:

procedure TFormI.TimerlTimer(Sender: TObject);

var DSCnt : Integer;

i : Integer; begin

WITH Session.Databases [0] do begin

DSCnt := DataSetCount; // Получаем количество открытых НД

FOR i := О ТО DSCnt - 1 do // Обновляем каждый из них

IF DataSets[i].State = dsBrowse THEN

DataSets[i].Refresh;

END;//with

end;

Если нужно обновлять лишь некоторые из наборов данных, указатели на них можно поместить в список TStringsListl. Этот список можно создать и наполнить в момент создания формы (но не в момент ее активизации, т.к. форма может активизироваться много раз - после минимизации или после вызова другого приложения). При разрушении формы список удаляется:

var

DSList : TStringList;

procedure TFormI.FormCreate(Sender: TObject);

begin

DSList := TStringList.Create; // Создаем список

DSList.AddOb]ect(' '/Tablel); // Помешаем в него ссылку на Tablel

DSList. Add0b;ect (' \Table2); // и Table2

Timeri.Enabled := True; // Включаем таймер

end;

procedure TFormI.TimerlTimer(Sender: TObject) ;

var i : Integer;

begin

WITH DSList do begin

FOR i := 0 TO Count - 1 do

IF (Objects [i] as TDataSet).State = dsBrowse THEN (Objects[i] as TDataSet).Refresh;

END;//with

end;

procedure TFormI.FormDestroy(Sender: TObject);

begin

DSList.Free;

end;

7.12. Обработка ошибок смены состоянии набора данных

В случае неудачи при выполнении методов Insert, Edit, Delete и Post обработку ошибки можно реализовать в соответствующих обработчиках событий OnEditError (ошибки при выполнении Insert и Edit}, OnDeleteError (ошибки при выполнении Delete} и OnPostError (ошибки при выполнении Post):

property OnEditError: TDataSetErrorEven;

property OnDeleteError: TDataSetErrorEven;

property OnPostError: TDataSetErrorEven;

где

TDataSetErrorEvent = procedure(DataSet: TDataSet; E: EDatabaseError;

var Action: TDataAction) of object;

TDataAction = (daFail, daAbort, daRetry);

назначение параметров:

DataSel - указатель на компонент, в котором произошла ошибка;

Е - ссылка на объект-исключение;

Action - действие:

daFail - выполнение метода, вызвавшего ошибку, отменяется, выводится сообщение об ошибке;

daAbort - выполнение метода, вызвавшего ошибку, отменяется, сообщение об ошибке не выводится;

daRetry - метод, вызвавший ошибку, после выхода из обработчика выполняется заново; при этом в теле обработчика должна быть скорректирована причина ошибки, иначе произойдет зацикливание программы.

Пример. Пусть необходимо выдать программное сообщение пользователю и отменить выполнение ошибочного метода, если возникает ошибка при выполнении метода Insert или Edit (например, таблица заблокирована другим пользователем). Тогда можно использовать такой обработчик события OnEditError:

procedure TFormI.TablelEditError(DataSet: TDataSet; E:

EDatabaseError;

var Action: TDataAction);

begin

ShowMessage('Таблица Сотрудников заблокирована другим ' + 'пользователем') ;

Action := daAbort;

end;

7.13. Ограничения на значения полей

Набор данных имеет свойство property Constraints: TCheckConstraints; которое представляет собой коллекцию компонентов TCheckConstraints. Каждый такой компонент определяет ограничение, накладываемое на значение одного или более полей. Число ограничений, созданных для НД, определяется свойством коллекции Constraints property Count: Integer; Доступ к отдельному ограничению с индексом Index осуществляется при помощи свойства property Items|Index: Integer): TCheckConstraint;

При этом значение Index должно находиться в диапазоне 0..Count - 1. На рис. 7.40 показан список ограничений, определенных для НД, как он выглядит при обращении к свойству Constraints набора данных в инспекторе объектов.

Каждое ограничение имеет тип TCheckConstraint. Рассмотрим свойства этого компонента.

property CustomConstraint: string;

Содержит текст ограничения на значение поля (полей) в SQL-подобном синтаксисе, например:

Table1.Constraints. .Items[i].CustomConstraint := 'Razrjad > 7 and Razr]ad < 15';

property ErrorMessage string; Содержит текст сообщения об ошибке. Это сообщение выводится, если пользователь предпримет попытку запомнить запись, поля которой не удовлетворяют данному ограничению (рис. 7.41).

property FromDictionary: Boolean; Указывает источник формирования ограничения - словарь данных (значение True) или непосредственно приложение (False).

property ImportedConstraint string; Используется для запоминания SQL-текста ограничения, импортированного из SQL-сервера или словаря данных.