    Глава 9 
    
    Работа с локальными базами данных 

    Марк Причард и Ноэль Райе 

    В предыдущих главах рассказывалось, как создавать приложения в Delphi для 
ввода данных пользователем, их обработки и вывода результатов. Вы ознакомились 
с различными типами приложений и множеством компонентов библиотеки VCL. 
    Эти знания помогут вам ориентироваться в утилитах и постоянно изменяющихся 
Web-броузерах, а также в устойчивых приложениях для обработки централизованных 
данных. Спрос на приложения, обрабатывающие централизованные данные, должен, 
подчиняясь принципам удовлетворения клиента и контроля качества, вырасти с 
расширением рынка. Где лучше сохранять адреса клиентов, информацию об их 
предпочтениях и покупках? Как контролировать потоки данных, которые возникают 
в повседневной работе? Как надежно сохранить эти данные? Ответ на все эти 
вопросы удивительно прост — используйте базу данных! 
    Реализация поддержки баз данных была непростым делом на протяжении 
десятилетий и позволила многим получить докторские степени в области 
вычислительной техники. Мы бы не смогли раскрыть данную тему и в десяти 
изданиях, а в этой главе ей отводится намного меньше места — всего пара глав. 
С учетом вышесказанного здесь будет приведена вводная информация о двух типах 
баз данных — локальных (local) и удаленных (remote), или SQL, а затем 
рассмотрена поддержка баз данных, предоставляемая в Delphi. 

    На заметку 
    Чтобы получить более подробную информацию о теории и проектировании баз 
данных, сходите в местный книжный магазин (и поищите книгу Кена Хендерсона 
Руководство разработчика баз данных в Delphi 2.— Прим. ред.). 

    Поддержка системы доступа к базам данных фирмы Borland (Borland Database 
Engine, или BDE) в Delphi организована просто великолепно. Можно разработать 
прило

    Введение в локальные базы данных 

    Здесь описываются понятия, связанные с локальными базами данных 
(таблицы,поля и записи), и рассматриваются два формата таблиц, поддерживаемых 
системой доступа к базам данных фирмы Borland (Borland Database Engine, или 
BDE), — Paradox И dBASE. 

    Компонентный интерфейс Delphi с BDE 

    Вы узнаете, как в Delphi инкапсулируются распространенные ФУНКЦИИ BDE В 
ДВуХ ПрОСТЫХ компонентах — ТТаЫе И TQuery. 

    Элементы управления интерфейса для приложений, обрабатывающих 
централизованные данные 

    Рассматриваются элементы управления библиотеки VCL, которые автоматизируют 
процесс передачи данных между набором данных (ТТаЫе ИЛИ TQuery) и интерфейсом приложения. 

    Дополнительные разделы 

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

    250 

    жение, используя локальную базу, такую как Paradox, а затем поместить его 
приложение на корпоративный сервер базы данных, когда оно будет работать 
надежно. В главе 10, "Работа с базами данных SQL", рассматриваются аспекты 
взаимодействия с SQL-серверами и поддержка в Delphi дополнительных 
функциональных возможностей. 

    Что такое локальные базы данных 

    Начнем с пары определений, чтобы быть уверенными, что мы понимаем друг друга. 
    База данных (database)— это совокупность данных, которые описывают 
некоторую систему, как в классическом примере управления товарами или 
материально-техническим снабжением. В базе данных хранится информация о 
количестве товаров, заказчиках, заказах, а также об отгрузках и перевозках. В 
хорошо спроектированной базе данных содержится несколько таблиц (tables), 
каждая из которых включает информацию об однотипных объектах системы. Объект 
клиента — лучший образец для таблицы. Какие свойства могут быть у клиента? 
Первое, что приходит в голову, — это номер счета и адрес. Эти свойства 
преобразуются в столбцы таблицы, называемые на языке баз данных полями 
(fields). Каждый клиент в системе представлен отдельной строкой таблицы, 
называемой записью (record). На рис. 9.1 показана таблица CUSTOMER. DB, 
содержащаяся в папке DEMOS \ DATA, которая, в свою очередь, находится в 
корневом каталоге Delphi. 

    i.» Table : C:\APPS\...\Cuttomei.db В 
    Customer'	CustNo j Company	Addit	Addi2 L?J 
    1.221.00:t<auai Dm Shoppe	4-976 Sugartoaf 1-tivy	Suite 103 р 
    2	U31.ro|Unisco	: PO Box Z-547 
    3	1.351.00;'SigM Diver	1 Neptune Lane 
    4|	1,354.001 Cayman Divers World Unlimited	;POBox541 
    5'	1,356.00 Tom Sawyer Diving Centre	632-1 Third Frydenhoj	} 
    Б	1,380.00' Blue Jack Aqua Center	|23-738 Paddmglon Lane	Suite 310 S 
    7	1,384.00 VIP Dryers Club	|32MainSt. "••t.^^i	,,.. ^Ui-.. ;„..<.„.:<„,:.J 
    8	1,510.00: Ocean Paradise	5 PO Box 8745 :^wBfil|SS	й^явййй!®! ' 
    9'.	1.513 00 Fantastique Aquatica	JZ32 999 !И2А-77АХ||Я1Я	ll?|B™iliM||;t^^ , 
    Ю	1,551.00. Marmot Dwers Club	'872 Queen St. ~^TT^~'" 
    It-	1,560.00,The Depth Charge	115243 Underwater Fwy.	I 
    12	1,563.00, Blue Sports	203 12th Ave. Box 746 
    13	1,624.00 Makai SCUBA Club	]POBoi<6534 
    14	1,645 OO'Action Club	PO Box 5451-F 
    15	1.651.00 Jamaica SCUBA Centre	•POBox68 : — : \ :	I 
    16	1,680.00 Island Finders	6133 W Stone Avenue • ;,,;:„	:;,^ „,',..;, 
,,,,,,, j 
    17:	1.984.00'Advenlure Undersea	: PO Box 744 , ^ЙДЙЙ	1Й№™йй, ^Sa.aSi1,,") : 
    18	2.118.00 Blue Sports Ctub	,63365 Nez Perce ЗПееГДИа	KySXS^ i 

    Рис. 9.1. Простая таблица клиентов 

    Каждая таблица локальной базы данных подобна документу или электронной 
таблице. Локальные таблицы сохраняются на жестком диске компьютера или 
централизованно записываются на сетевой диск. Таблицы можно перемещать с 
помощью программы Explorer или копировать с помощью стандартной программы 
резервного копирования. Отличие локальных таблиц от документов и электронных 
таблиц заключается в том, что они могут использоваться несколькими 
приложениями одновременно (т.е. поддерживают совместный доступ). 
    Чтобы управлять совместным использованием данных, необходим набор процедур 
для контроля и защиты доступа к записям и таблицам, например если два 
пользователя пытаются получить доступ к одной и той же записи для ее 
изменения. Общих методов для такого контроля не существует — каждый тип базы 
данных (локальной или удаленной/SQL) обеспечивает выполнение протокола 
блокировки своим собственным способом. 

    Типы локальных баз данных 

    Система BDE поддерживает, как свои собственные, два типа локальных таблиц 
— Paradox и dBASE. Каждый из них имеет свои преимущества и недостатки. 

    Преимущества таблиц баз данных Paradox 

    Таблицы Paradox постоянно развиваются и предоставляют много возможностей. 

    Множество типов полей 

    Таблицы Paradox поддерживают более 15 типов полей: от числовых до 
текстовых с фиксированной и переменной длиной и полей, позволяющих сохранять 
непосредственно двоичные данные. Такой большой набор типов полей позволяет 
гибко выбирать параметры проекта базы данных и более точно представлять 
сохраняемую информацию. 

    Глава 9. Работа с локальными базами данных 251 

    Предположим, необходимо сохранить значения Yes/No или True/False. Для 
этого можно использовать текстовое поле с одним символом (значение Y для Yes и 
N — для No) или поле для малого целого числа (нулевое значение для No и 
ненулевое значение — для Yes). Проблема при таком выборе состоит в том, что в 
поле можно сохранить значение Z, у или #. Лучшим выбором для такого случая 
является Boolean (логический). 

    Целостность данных 

    Концепция целостности данных (referential integrity) обеспечивает 
правильность ссылок между таблицами. Использовать приложения управления 
заказами, как в нашем примере, удобно в том случае, когда клиенты делают 
заказы. Каждому клиенту присваивается уникальный номер. Этот номер заносится в 
таблицу клиентов (что вполне естественно) и в таблицу заказов (чтобы можно 
было узнать, какой клиент сделал данный заказ). Если вы удалите запись клиента 
из таблицы клиентов, то соответствующая запись в таблице заказов будет иметь 
ссылку на несуществующего клиента. Возникновение таких ситуаций крайне нежелательно. 
    Можно вручную написать программу для удаления всех ссылок на конкретного 
клиента из каждой таблицы базы данных. Такое удаление называют каскадным 
(cascading delete), но зачем эти хлопоты? Для таблиц Paradox система BDE 
сделает это вместо вас, сэкономив ваше время. 

    Сохранность паролей 

    Если вы пишете приложение, в котором используются секретные данные, то, 
вероятно, вы перед записью в базу данных будете зашифровывать их вручную. Хотя 
это и приемлемо для небольших таблиц, но представьте себе, какие потребуются 
усилия для десяти таблиц с двадцатью полями в каждой! Как обеспечить защиту 
каждого поля? И опять-таки Paradox выполнит эту работу. Для таблицы Paradox 
можно задать основной пароль, который будет использоваться для любых типов 
операций над всей таблицей, таких как редактирование данных в любом поле или 
изменение структуры таблицы. Вспомогательный пароль предоставляет 
дополнительные возможности, например ограниченный доступ к подмножеству полей 
или разрешение пользователям только добавлять записи. 

    Простая проверка правильности данных 

    Большинство значений полей заключено в определенный диапазон (range), как, 
например, поле Аде (возраст). Очевидно, что в этом случае любое отрицательное 
значение будет неправильным, как и любое значение, превышающее 130. Как и в 
случае обеспечения целостности данных такое ограничение можно 
запрограммировать вручную, однако в системе, содержащей более одной программы 
и следящей за выполнением подобного ограничения, могут возникнуть затруднения. 
Таблицы Paradox позволяют перенести логику базы данных из программы в саму 
базу данных, где она и реализуется. 
    Для каждого поля можно определить минимальное и максимальное значения, а 
также значение по умолчанию. Минимальное и максимальное значения заключают 
содержимое поля в определенный диапазон. Если ввести значение, которое выходит 
за пределы такого диапазона, Delphi сгенерирует исключительную ситуацию и 
отвергнет неправильное значение. Значения по умолчанию автоматически 
присваиваются полям при добавлении новых записей. 

    Недостатки таблиц баз данных Paradox 

    На первый взгляд, таблицы Paradox — самые лучшие среди локальных таблиц. 
Однако не все так просто. Существует два момента, которые необходимо учитывать 
при их использовании. 

    Большое количество файлов 

    Таблицы Paradox сохраняются в нескольких файлах, имеющих разные 
расширения, которые указывают на их назначение (табл. 9.1). 

    Таблица 9.1. Расширения файлов для таблиц Paradox 

    Расширение Описание 

    DB          Таблица данных. Файл необходимо восстановить, если произошел 
сбой компьютера 
    MB          Данные больших двоичных объектов (Binary Large Object — BLOB). 
Файл используется для Мемополей, форматированных мемо-полей, графических и 
двоичных полей, а также для OLE-полей 
    РХ           Первичный индекс. Файл используется для контроля за исходным 
порядком сортировки и поиска в таблице 
    XG?, YG?   Вторичные индексы. Файлы используются для управления 
альтернативным порядком сортировки и поиска в таблице 
    VAL         в файле сохраняются установки для проверки достоверности 
данных и целостности ссылок 
    TV          в файле сохраняется форма вывода таблицы в системе Database 
Desktop, например такие параметры, как порядок столбцов и их ширина 
    FAM         Используется вместе с TV-файлом 


    252                                     Часть III. Работа с базами данных 

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

    Файлы с расширением NET 

    Файлы с расширением NET служат для контроля доступа к базам данных 
Paradox. Если всем пользователям необходима какая-то таблица, они должны 
использовать один и тот же NET-файл. В небольших сетях с одним файл-сервером 
это обычно не вызывает проблем — просто создайте отдельную папку для 
сохранения NET-файла и предоставьте каждому пользователю все права доступа к 
ней. В больших сетях с несколькими файл-серверами Paradox добавляет небольшие 
административные накладные расходы — все пользователи базы данных должны иметь 
доступ к серверу, содержащему NET-файл. 

    На заметку 
    Глобальный объект Session (автоматически создаваемый экземпляр класса 
TSession) позволяет указывать различное расположение NET-файла для каждого 
приложения. Дополнительную информацию можно найти в описании свойства 
NetFileDir класса TSession. 
    Таблицы dBASE не используют NET-файла, который применяет Paradox. 

    ПРЕДОСТЕРЕЖЕНИЕ 
    Если обратиться к базе данных, контролируемой с помощью другого МЕТ-файла, 
Delphi сгенерирует исключительную ситуацию. 


    Параметры таблиц Paradox 

    При создании таблицы Paradox можно настроить некоторые параметры. 
Модификация выполняется с помощью программы BDE Administrator, диалоговое окно 
которой показано на рис. 9.2. Файл программы BDE Administrator называется 
BDEADMIN. ЕХЕ и находится в папке, в которой установлена система BDE. 

    | -'•BDE Adoiiniilidtoi 
    ДЬ|ес1 Edit View Ofitions Help 
    &: X ю ^ (§ 
    1 АН Database Afiases	Definition otDefautDD 
    . Config Database® |	Definition | 
    :' В' ^ Databases :; i SQ DBDEMOSj! !• ^|ИЯЗЭ31	TypePATH DEFAULT 
DRIVER	STANDARD C:\Appt\Ddpni32'\OBJREPOS 'PARADOX 
    || :" SQ SalonEasy //. 

    Рис. 9.2. Программа BDE Administrator позволяет настроить систему BDE 
    
    Важными являются следующие параметры. 
    • Block Size (Размер блока). Максимальный физический размер файла (в 
мегабайтах) для таблицы Paradox вычисляется на основе параметра Block Size. 
Параметр должен быть кратен 1 024 байт и может быть равен максимальной 
величине (32 768). Зависимость между размером файла и размером блока следующая: 
    PhysicalFileSize = Block Size / 1024 * 64 

    Глава 9. Работа с локальными базами данных 253 

    Fill Factor (Коэффициент заполнения). Этот параметр определяет, на сколько 
процентов должен использоваться текущий индексный блок, прежде чем система BDE 
выделит следующий. Уменьшение этого параметра приводит к повышению 
эффективности работы таблицы за счет дискового пространства. 
    Strict Integrity (Ограничение целостности). Эта опция применяется при 
работе с приложениями, которые не проверяю! целостности ссылок (например, они 
используют старую версию системы BDE). Если для параметра установлено значение 
False, то такое приложение может считывать и записывать данные в базу данных, 
нарушая целостность данных. Если же значение равно True, то приложение может 
лишь считывать данные из таблицы базы данных. 

    Преимущества таблиц dBASE 

    Формат таблиц dBASE — это один из первых форматов PC-таблиц, и поэтому он 
поддерживается практически всеми приложениями, которые связаны с данными, 
имеющими формат таблиц. Использование формата файлов dBASE предоставляет 
несколько дополнительных преимуществ, о которых будет рассказано в следующих разделах. 

    Ограниченное количество физических файлов 

    По сравнению с базами данных Paradox, базы данных dBASE имеют ничтожно 
мало физических файлов. В табл. 9.2 указаны функции каждого из физических dBASE-файлов. 

    Таблица 9.2. Расширения файлов для таблиц dBASE 

    Расширение Описание 

    DBF                  Данные таблицы 
    DBT                  BLOB-данные (Мемо-, двоичные и OLE-поля) 
    MDX                   Используется для управления набором поддерживаемых индексов 
    NDX                    Сохраняет набор неподдерживаемых индексов 

    Такое ограниченное число файлов облегчает резервное копирование и 
обслуживание файлов. 

    Дополнительные функции индексов 

    Как уже упоминалось, индекс используется, чтобы обеспечить порядок 
сортировки для таблицы. Если, например, нужно выполнить поиск по имени 
клиента, установите индекс для поля имени заказчика, чтобы ускорить процесс 
поиска. В базах данных dBASE поддерживаются следующие две дополнительные опции 
для индексов. 
    • Expression Indexes (Составной индекс). Эта опция позволяет создать 
индекс, основанный на комбинации нескольких полей. Например, таблица 
заказчиков имеет поля FIRSTNAME (имя) и SURNAME (фамилия). Составной индекс 
SURNAME+FIRSTNAME образует новое поле, которое содержит конкатенацию значений 
записи для двух полей. Это новое поле формирует основу составного индекса. 
Преимущество использования составного индекса заключается в том, что поиск 
нужно выполнять только по одному полю, хотя необходима пара полей. Недостаток 
же состоит в том, что значения полей объединены, так что поиск и перебор всех 
записей, содержащих определенную фамилию, становится более сложными. 
    U Subset Index (Индекс поднабора данных). Фактически это фильтр. Он 
определяет набор записей, которые удовлетворяют конкретному критерию. Снова 
используя таблицу заказчиков, можно было бы создать индекс поднабора данных с 
полем SURNAME, равным, например, Blogs, С помощью этого индекса можно 
автоматически отбросить все записи, в которых значение в поле фамилии не 
совпадает с Blogs. Преимущество использования индекса поднабора данных 
заключается в том, что упрощается логика приложения, которое обрабатывает 
определенный поднабор данных (вместо проверки каждой записи всего набора на 
совпадение проще применить индекс поднабора данных для таблицы и затем 
обработать все записи поднабора). Недостаток же этого метода состоит в том, 
что необходимо создавать такие индексы для каждого из подобных используемых фильтров. 

    Неполное удаление 

    Удаляемые из таблиц dBASE записи лишь помечаются как удаленные, но 
физически не удаляются. Записи, помеченные как удаленные, игнорируются при 
поиске и переборе записей таблицы. 
    Для восстановления удаленной записи используется API-функция системы BDE— 
DbiUndeleteRecord. Чтобы физически удалить все записи, помеченные как 
удаленные, применяют функцию DbiPackTable. Более подробная информация 
содержится в файле BDE32.HLP, расположенном в папке системы BDE. 

    254 Часть III. Работа с базами данных 


    Недостатки таблиц dBASE 

    Таблицы dBASE не только не поддерживают целостности ссылок, достоверности 
данных и защиты данных, но имеют еще пару недостатков. 
    • Короткие имена полей. Имена полей могут состоять не более чем из восьми 
символов и не допускают использования пробелов. 
    • Ограниченное количество типов полей. В таблицах dBASE поддерживается 
лишь восемь типов полей — символьные, числа с плавающей запятой, числовые, 
даты, логические, Мемо-, OLE- и двоичные поля. Этот ограниченный набор 
означает, что нельзя использовать автоматическое форматирование значений полей 
при восстановлении данных из таблицы. В качестве примера можно привести 
отсутствие денежного типа, которое вынуждает в приложении специально 
форматировать числовое поле dBASE как денежное, в то время как в Paradox для 
денежного типа это форматирование выполняется автоматически. 

    Paradox и dBASE: что лучше? 

    Не считая уже упоминавшихся преимуществ и недостатков Paradox и dBASE, 
необходимо учитывать следующее. 
    • Простота перемещения базы данных. Проверка целостности ссылок, 
достоверности данных, задание значений по умолчанию, парольная защита — все 
это поддерживается в Paradox и в SQL-базах данных. Такие общие атрибуты 
упрощают перемещение с локальной базы данных Paradox на SQL-сервер. 
    • Производительность. Базы данных Paradox несколько превосходят базы 
данных dBASE по производительности. Вставка и изменение записей, как и поиск 
по индексированным и неиндексированным полям, в базах данных Paradox 
выполняется быстрее. 
    В общем, можно сделать вывод, что, если у вас не возникает проблем с 
NET-файлами, целесообразнее использовать базы данных Paradox. 
    Компоненты доступа к данным 
    После выбора типа таблицы можете приступить к чтению этого раздела, в 
котором рассказывается о компонентах, упрощающих использование системы BDE. 

    Компоненты TTable и TQuery 

    С помощью класса ТТаЫе можно получить доступ к логическим таблицам, а с 
помощью класса TQuery — сгенерировать результирующую таблицу, основанную на 
SQL-запросе. Логические таблицы и результурующие таблицы, соответствующие 
SQL-запросу, называют наборами данных (datasets). Вследствие такой унификации 
компоненты ТТаЫе и TQuery совместно используют многие свойства, события и методы. 

    Свойства 

    В классах ТТаЫе и TQuery объявляется несколько свойств, которые отражают 
состояние лежащего в их основе набора данных. 

    Свойство Active 

    Свойство Active использует логическое значение, чтобы определить, открыт 
ли набор данных. Если значение свойства равно True, то набор данных открыт и 
может использоваться. Методы Open и Close выполняют те же функции, что и 
модификация свойства Active, и являются более простыми для понимания. В 
следующем фрагменте программы показано использование этих методов для объекта 
tbICust класса ТТаЫе. 
    { Открыть таблицу заказчиков, установив свойство Active. } tbICust.Active 
:= True; 
    { Закрыть таблицу заказчиков. } tbICust.Active := False; 
    ( Открыть таблицу заказчиков с помощью метода Open. } tbICust.Open; 
    { Закрыть таблицу заказчиков. } tbICust.Close; 


    Глава 9. Работа с локальными базами данных                             255 


    ПРЕДОСТЕРЕЖЕНИЕ 
    Закрытие набора данных автоматически не сохраняет текущую запись. Для 
сохранения изменений перед вызовом метода close используйте метод post или 
установите значение свойства Active равным False. 

    Свойства BOF и EOF 

    Эти свойства указывают, где находится текущая позиция в наборе данных: в 
начале (BOF) или в конце (EOF). Свойство BOF равно True в следующих случаях. 
    Таблица в сессии открыта впервые. 

    Вызван метод First. 
    Вызван метод Prior (указатель установлен в первой записи). 
    j   Свойство EOF равно True при следующих условиях. 
    !,   • Открыта пустая таблица. 
    '— И Вызван метод Last. 
    • Вызван метод Next (указатель установлен в последней записи). 

    Чаще всего свойство EOF применяется для перебора всех записей таблицы. В 
следующем фрагменте программы вычисляется сумма целых числовых значений поля 
Аде (возраст), размещаемых в наборе данных qryResult класса TQuery. 

    var 
    iSum : Integer; 
    begin 
    { Инициализация суммы. } 
    iSum := 0; 
    { Переход к первой записи. } qryResult.First; 
    { Перебор всех записей набора данных. ) 
    while not qryResult.Eof do 
    begin 
    { Увеличение переменной суммирования. ) 
    Inc(iSum, qryResult.Fields у Name('Age').As Integer) ; 
    { Переход к следующей записи. } qryResult.Next ; 
    end; 
    end; 


    Свойство DatabaseName 

    Это свойство определяет базу данных, благодаря которой можно получить 
доступ к набору данных. Это может быть BDE-псевдоним, например DBDEMOS, или 
путь к папке с базой данных Paradox или dBASE. Обычно это значение 
устанавливается в режиме проектирования притгожетптаг; '"""  " '""      

    Свойства DBHandle и Handle 

    С помощью этих свойств можно непосредственно вызывать функции программного 
интерфейса BDE. Это может понадобиться в случае, если вас по каким-либо 
причинам не устраивают компоненты баз данных Delphi. 
    Свойство DBHandle возвращает внутренний идентификатор BDE для базы данных, 
содержащей таблицу, в то время как свойство Handle возвращает внутренний 
идентификатор для набора данных. В следующем фрагменте программы 
подразумевается, что объект tbICust класса TTabJ-e существует и корректно 
инициализирован. Вызывается функция программного интерфейса DbiPackTable для 
изменения структуры таблицы и удаления неиспользующихся зап"и5йГ 

    C^cJc (Dbi PackTable (tbICust. DBHandle, tbICust. Handle, "'""^ 
Nil,Nil,True) ) ; 


    256 Часть III. Работа с базами данных 


    Свойства Fields и FieldCount 

    Свойство Fields возвращает массив объектов TField, которые позволяют 
обращаться к каждому полю по его индексному значению. Свойство FieldCount 
возвращает число объектов TField в массиве. В следующем фрагменте программы 
добавляется список имен полей из объекта tbIStock класса ТТаЫе в список 
IbFieldList класса TListBox. 

    var 
    iCount : Integer; 
    begin 
    ( Очистка элементов в списке класса TListBox. } 
    IbFieldList Clear; 
    { Перебор массива полей. } for iCount := 0 to tbIStock.FieldCount - 1 do 
    { Добавить имя поля в список класса TListBox. ) 
IbFieldList.Items.Add(tbIStock.Fields[iCount].FieldName) ; 
    end; 


    Свойство Modified 

    В свойстве Modified используется логическая переменная, указывающая, 
изменена ли текущая запись (она не должна быть записана в набор данных). В 
следующем коде проверяется, была ли сохранена запись объекта qryResult класса 
TQuery, и выясняется, нужно ли ее сохранять. 

    If qryResult.Modified then if MessageDIg('Save record?',mtConfirmation,[mbYes,mbNo],0) 
    = mrYes then qryResult.Post; 


    Свойство RecordCount 

    Свойство RecordCount возвращает число записей в наборе данных и 
используется для отображения состояния обработки набора записей. В следующем 
фрагменте программы перебираются все записи объекта tbICust класса TTable и 
изменяется элемент управления pnlProgress класса TPanel для вывода процента 
обработанных записей. 

    var 
    iRecNo, iRecCount : Iteger; 
    begin 
    ( Установить число записей в таблице. } 
    iRecCount := tbICust.RecordCount; 
    { Переход к первой записи. } iRecNo := 1; 
    tbICust.First; 
    { Перебор всех записей таблицы. } 
    while not tbICust.EOF do 
    begin 
    { Изменение панели прогресса. } 
    pnlProgress.Caption:=IntToStr(100 * iRecNo div iRecCount) + '%'; 
    { Здесь обрабатываются записи. } 
    { Переход к следующей записи. } tbICust.Next; 
    Inc(iRecNo) ; 
    end; 
    end; 


    Методы 

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

    Глава 9. Работа с локальными базами данных 
    9 Использование Delphi 3. Специальное издание 257 


    Метод FieldByName 

    Метод FieldByName предоставляет доступ к полю в таблице по имени поля, в 
отличие от индекса в массиве Fields. Это означает что ваш код не будет 
опираться на лежащую в основе структуру таблицы и будет более понятным. В 
следующем фрагменте программы показано использование метода FieldByName, при 
этом подразумевается, что поле CustNo — первое поле в таблице (имеет индекс, 
равный 0 в массиве Fields). 

    { Перевести набор данных в режим редактирования. } 
    tbICust.Edit; 
    { Установить поле CustNo равным 100 с помощью метода FieldByName. } 
     tbICust.FieldByName('CustNo').Aslnteger := 100; 
    { Установить поле CustNo равным 100 с помощью Fields[О]. } 
    tbICust.Fields[0].Aslnteger := 100; 

    Если вы — 4)aнaт скорости, обратите внимание на потерю^ производительности 
при использовании метода FieldByName. Однако незначительное снижение 
производительности компенсируется значительным повышением удобочитаемости кода. 

    Методы Edit, Insert, Post и Cancel 

    Эти методы позволяют управлять вставкой и редактированием записей, а также 
сохранять или игнорировать изменения. Метод Edit блокирует текущую запись для 
редактирования. Если вы попытаетесь записать данные в запись, которая не 
переведена в режим редактирования, Delphi сгенерирует исключительную ситуацию 
и отвергнет изменения. Если ваш 'вызов"метода Edit выполнен успешно, а другое 
приложение пытается редактировать эту же запись, это приложение получит 
сообщение об ошибке и его запрос на редактирование будет проигнорирован. 
    Метод Insert создает пустую запись в наборе данных и автоматически 
переводит ее в режим редактирования. После окончания редактирования записи 
можно вызвать MerozTPost для сохранения записи в наборе данных или метод 
Cancel —для отмены изменений. 
    В следующем фрагменте программы вставляется новая запись таблицы tbICust 
класса ТТаЫе и запрашивается подтверждение сохранения записи. 

    { Вставить новую запись. ) tbICust.Insert; 
    { Проверить, нужно ли сохранять запись. ) 
    if MessageDIg('Save record',mtConfirmation,[mbYes,mbNo],0) = mrYes then 
    ( Сохранение записи. } 
    tbICust.Post; 
    else 
    { Проигнорировать запись. } 
    tbICust.Cancel; 


    Методы First, Last, Prior, Next и MoveBy 

    Эти методы позволяют программным путем перемещаться по набору данных. 
Методы First (первый), Last (последний), Prior (предыдущий) и Next 
(следующий), должно быть, в разъяснениях не нуждаются. 
    Метод MoveBy позволяет пропустить несколько записей сразу, в отличие от 
методов Next и Prior, которые предоставляют возможность перемещаться лишь на 
одну запись за один вызов. Метод MoveBy возвращает число записей, которые 
фактически пропущены, поэтому можно проверить, не достигнуто ли начало таблицы 
или ее конец при перемещении. В следующем фрагменте программы выводится 
сообщение при попытке пройти последнюю запись таблицы. 

    tbICust.Last; 
    if tbICust.MoveBy(5) о 5 then MessageDIg('Unable to move forward five records'); 


    Метод Locate 

    Метод Locate позволяет найти первую запись, удовлетворяющую определенному 
критерию. В методе используется три аргумента в следующем порядке. 
    1. Имя поля, по которому выполняется поиск. 
    2. Поисковое значение (целое, строка, вещественное и т.д.). 

    258                                      Часть III. Работа с базами данных 

    3. Набор TLocateOptions, который используется для настройки поиска. Член 
loCaselnsensitive переводит поиск в режим, не различающий строчных и прописных 
букв (режим, чувствительный к прописным и строчным буквам устанавливается по 
умолчанию). Параметр loPartialKey означает, что система BDE будет воспринимать 
как удовлетворяющие критерию записи, начало значений полей которых совпадает. 
Например, John подходит при поиске Jo, a Doe — при поисковом значении Do. 

    На заметку 
    Если используется параметр loPartialKey, при поиске будет игнорироваться 
различие между прописными и строчными буквами, даже если не применялся член loCaselnsensitive. 

    Метод Locate имеет следующие преимущества по сравнению с методом FindKey 
(метод класса ТТаЫе). 
    Я Метод Locate не требует изменения индексов для поиска по другому полю. 
Метод FindKey требует, чтобы индекс, по которому выполняется поиск, 
соответствовал текущему порядку сортировки таблицы. 
    В Поиск методом Locate предполагает, что вы можете забыть о создании 
составного индекса (образованного из нескольких полей) для всех возможных 
типов поиска. Вернее, можно просто создать отдельные индексы для полей, по 
которым необходим поиск — система BDE достаточно интеллектуальна и может 
использовать рее доступные индексы, если это возможно. 
    Г Метод Locate можно использовать для наборов данных обоих классов: TQuery 
и ТТаЫе. Метод FindKey можно использовать лишь для наборов данных класса 
ТТаЫе.                            "~—;"' - '""• 

    События 

    В классах ТТаЫе и TQuery совместно используется один и тот же набор 
событий, которые сигнализируют об изменении состояния набора данных. В этом 
разделе рассматриваются наиболее распространенные события. Чтобы получить 
более детальную информацию об особенностях событий наборов данных, обращайтесь 
к разделам, описывающим классы ТТаЫе и TQuery в электронной справке. 

    События BeforeXXX 

    Набор событий BeforeXXX состоит из следующих событий: BeforeCancel, 
BeforeClose, BeforeDelete, ВеforeEdit, Beforelnsert, BeforeOpen, BeforePost и 
BeforeScroll. Неудивительно, что каждое из них происходит перед определенным 
действием, на которое ссылается имя этого события. 
    Если вы назначите обработчик событий для события BeforeDelete, то он будет 
запускаться при вызове метода Delete, но выполняться будет перед тем, как 
система BDE действительно удалит запись. Если будет сгенерирована 
исключительная ситуация в обработчике события BeforeDelete, удаление не будет 
выполнено. В следующем фрагменте программы показаны эти действия. 

    procedure TForml.tbICustBeforeDelete(DataSet: TDataSet); 
    begin 
    ( Проверить, сделал ли заказ клиент. } 
    if tbIOrders.FindKey ([DataSet.FieldByName('CustNo').Aslnteger]) then ( 
Возбудить HCKnB4H^nb^^,_.ci^y.aJJajs^^a^OTMeH^^y^       } raT?e~E'xceptTon. 
Create ('Unable to delete customer .'^TT" end; 

    Этот фрагмент обеспечивает элементарную защиту целостности данных. Здесь 
проверяется, имеет ли клиент заказ в таблице заказов, и, если имеет, удаление 
записи о клиенте запрещается. 
    Напомним, что обработчик событий может разделяться несколькими объектами. 
Однако обращение к нему происходит через класс набора данных TDataSet (ТТаЫе 
или TQuery), в котором запускается обработка события, упрощая обработку 
соответствующего набора данных. 

    События AfterXXX 

    Набор событий AfterXXX состоит из следующих событий: AfterCancel, 
AfterClose, AfterDelete, AfterEdit, Afterlnsert, AfterOpen, AfterPost и 
AfterScroll. Они подобны набору событий BeforeXXX и сигнализируют об 
определенных изменениях в наборе данных. Различие заключается в том, что их 
обработка запускается после того, как произойдут изменения в наборе данных. 
Чаще всего эти события используются для вывода сообщений о состоянии. В 
следующем коде обновляется элемент управления Т Panel, при этом панель 
состояния используется для вывода сообщения после регистрации записи в таблице. 

    procedure TForml ,'tblCustAfterPost (DataSet: TDataSet); 
    begin 
    { Обновить элемент управления TPanel. } 
    pnlStatus.Caption := 'Record saved successfully'; 
    end; 


    Глава 9. Работа с локальными базами данных                             259 


    Событие OnCalcFields 

    В Delphi можно определить вычисляемые поля (calculated fields), т.е. поля, 
которые созданы, но физически не представлены в таблице. Значения им 
присваиваются во время выполнения приложения. Событие OnCalcFields дает 
возможность генерировать значения для вычисляемых полей. 
    Прежде чем установить значение для вычисляемого поля, его необходимо 
определить. Выполнив приведенную ниже последовательность действий, можно 
определить вычисляемое поле. 
    1. Щелкните правой копкой мыши на пиктограмме соответствующего набора 
данных (ТТаЫе или TQuery). 
    2. Выберите команду Fields Editor (Редактор полей) в контекстном меню. 
Появится окно редактора полей, как показано на рис. 9.3. 
    3. Щелкните правой кнопкой в окне редактора полей и выберите команду New 
Field (Новое поле). Появится диалоговое окно New Field, показанное на рис. 9.4. 

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

    Рис. 9.4. Диалоговое окно New Field позволяет определить поле пользователя 
для набора данных 

    4. Заполните диалоговое окно соответствующей информацией. Не забудьте 
выбрать тип Calculated в группе Field type (Тип поля). Щелкните на кнопке ОК, 
и новое поле будет добавлено в ваш набор данных. 
    Предположим, что мы создали поле UpperName, которое будет содержать 
значение поля Name, записанное прописные буквами. Для того чтобы заполнить 
поле UpperName, можно использовать следующий обработчик событий. 

    procedure TFormI.tbCustCalcFields(DataSet: TDataSet) ; 
    begin 
    ( Вычисляем значение поля. } with DataSet do 
    FieldByName('UpperName').AsString := 
    Uppercase(FieldByName('Name').AsString) ; 
    end; 


    Подробнее о классе TTable 

    В классе ТТаЫе представлено несколько свойств и методов, которые не 
поддерживаются классом TQuery. 

    Свойства 

    В этом разделе описывается несколько распространенных свойств, которые 
являются уникальными для класса ТТаЫе. 

    Свойство TableName 

    Это одно из наиболее важных свойств для объектов класса ТТаЫе. Свойство 
TableName задает для объекта лежащую в основе таблицу. Если попытаться открыть 
таблицу класса TTable, свойство TableName которой установлено неверно, система 
BDE выдаст ошибку. Свойство TableName обычно устанавливается во время 
разработки проекта с применением средства Delphi Object Inspector. 

    260 Часть III. Работа с базами данных 


    Свойство Exclusive 

    Свойство Exclu5ive использует логическое значение для определения, открыта 
ли основная таблица в монопольном режиме. Если ваше приложение имеет 
монопольный доступ к таблице, то никакие другие приложения не могут считывать 
данные из таблицы или выполнять в нее запись. Эта установка полезна, если 
приложение должно выполнять определенную критическую обработку данных и 
требуется, чтобы данные в это время не изменялись. 
    Чтобы открыть таблицу в монопольном режиме, необходимо установить для 
свойства Exclusive значение True, когда таблица неактивна, как показано в 
следующем фрагменте программы. 

    with tbICust do begin 
    { Закрыть таблицу. } 
    Close; 
    { Установить монопольный режим. } tbICust.Exclusive := True; 
    { Повторно открыть таблицу, но уже в монопольном режиме. } tbICust.Open; 
    end; 

    Свойство IndexName 

    Свойство IndexName указывает текущий порядок сортировки для таблицы. По 
умолчанию значение свойства IndexName устанавливается равнымнулю, а это 
означает, что система BDE будет использовать первичный индекс для сортировки 
таблицы. Можно переключиться на другой порядок сортировки путем установки 
значения свойства IndexName равным имени вторичного индекса. Например, возьмем 
снова старую добрую таблицу заказчиков. Каждый заказчик имеет 
утшкальнЫТГидентификатор, который используется как первичный ключ таблицы. 
Поскольку необходимо выполнять поиск в таблице по имени заказчика, создадим 
вторичный индекс по полю Name, который назовем Name Index. В следующем коде 
выполняется переключение на индекс Name Index и определяется запись с 
заказчиком по имени Do, которое заменяется на Doe. Затем снова происходит 
переключение к первичному индексу. 

    with tbICust do begin try 
    { Установить порядок сортировки по индексу Namelndex. } tbICust.IndexName 
:= 'Namelndex'; 
    { Поиск записи с заказчиком Do. ) if tbICust.FindKey(['Do']) then begin    
         ^   V-' 
    ( Правка записи заказчика. ) Edit; 
    { Модификация поля. } FieldByName('Name').AsString :='Doe' ; 
    ( Зарегистрировать запись в таблице. ) Post; 
    end; 
    finally 
    { Перейти снова на первичный индекс. } tbICust.IndexName := ''; 
    end;                     -~: 
    end; 


    Методы 

    В классе TTable объявлено несколько методов для работы с таблицей. В этом 
разделе рассматриваются самые популярные методы. Менее известные методы 
описываются в электронной справочной системе. 

    Глава 9. Работа с локальными базами данных                             261 


    Метод LockTable 

    Метод LockTable ограничивает уровень доступа к таблице, который 
предоставляется другим приложениям. Для таблицы можно установить два типа блокировки. 
    К Read. Система BDE не позволяет другим процессам устанавливать блокировку 
для записи в таблицу, пока действует блокировка для чтения. Этот тип 
блокировки используется, если необходимо гарантировать доступ к таблице по 
чтению и записи и не допустить монопольной записи в таблицу другим процессом. 
Блокировка для чтения устанавливается с помощью константы ItReadLock. 
    • Write. Блокировка для записи не позволяет другим процессам устанавливать 
любой тип блокировки для таблицы. Эта блокировка отличается от монопольного 
режима. Монопольный режим запрещает доступ к таблице, тогда как блокировка для 
записи просто означает, что другие приложения не могут устанавливать 
блокировку для чтения или записи в таблицу. Этот тип блокировки используется, 
если необходимо гарантировать, что только ваше приложение будет выполнять 
запись в таблицу, но совместный доступ к таблице не запрещается. Блокировка 
для записи устанавливается константой ItWriteLock. 
    В следующем фрагменте программы устанавливается блокировка для записи на 
таблицы товаров во время повышения всех цен на 10%. 

    with tbIStock do begin 
    { Блокировка таблицы. } 
    LockTable(ItWriteLock) ; 
    try 
    ( Переход к первой записи таблицы. } First; 
    { Перебор всех записей таблицы товаров. } 
    while not EOF do 
    begin 
    { Увеличение цены на 10%. } 
    FieldByName('Price').AsFloat := 
    FieldByName('Price').AsFloat * 1.1; 
    { Перейти к следующей записи. } 
    Next; 
    end; 
    finally 
    { Снять блокировку для записи с таблицы. } UnlockTable(ItWriteLock) ; 
    end; 
    end; 


    Метод SetRange 

    Метод SetRange используется для фильтрации данных таблицы и применяется в 
случае, если необходимо обрабатывать лишь подмножество записей таблицы. 
    Для локальных таблиц, таких как Paradox и dBASE, поле, по которому 
осуществляется фильтрация, должно быть индексированным. 
    В следующем примере индекс таблицы переключается на индекс 
PurchaseDateIndex, а затем применяется фильтр по датам: с 1/1/1997 по 31/12/1997. 

    with tbIOrder do 
    begin 
    { Установить текущий индекс по полю Purchase Date (Дата закупки). } 
    IndexName := 'PurchaseDateIndex'; 


    262                                     Часть III. Работа с базами данных 


    try 
    { Применить фильтр диапазона данных. } 
    SetRangef[EncodeDate(1997,1,1)],[EncodeDate(1997,12,31)]) ; 
    { Выполнить обработку поднабора данных здесь. } 
    finally 
    { Восстановить первичный индекс. } 
    IndexName := '' ; 
    end; 
    end; 


    Подробнее о классе TQuery 

    Класс TQuery позволяет использовать операторы языка SQL для получения 
доступа к локальной или удаленной базе данных. Этим обусловлено появление 
новых свойств и методов.                             1 

    Свойства 

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

    Свойства Params и ParamCount 

    Язык SQL позволяет определить несколько параметров (parameters), которые 
используются при выполнении определенного SQL-оператора. Массив свойств Params 
содержит набор параметров для запроса, а свойство ParamCount — количество 
элементов этого массива. Более подробно данные свойства описаны в главе 10, 
"Работа с базами данных SQL". 

    Свойства RequestLive и CanModify 

    В свойстве RequestLive используется логическое значение для определения, 
каким должен быть возвращаемый результат— изменяемым набором данных или 
набором только для чтения. Если установить значение свойства RequestLive 
равным True и выполнить запрос, то система BDE возвратит набор данных, который 
можно редактировать. Значение свойства RequestLive по умолчанию равно False. 
Значение свойства CanModify равно True, если возвращаемый набор данных можно 
изменять, и False, если этот набор предназначен только для чтения. 
    „        Установка свойства RequestLive равным True не гарантирует, что 
возвращаемый набор данных можно ; будет модифицировать. Чтобы быть изменяемым 
для SQL-операторов, набор данных должен удовлетворять ряду требований, среди 
которых, например, требование, чтобы в наборе данных не использовались 
операции суммирования или вычисляемые поля. 

    Свойство UniDirectionaI 

    Это логическое свойство используется для экономии памяти вашим 
приложением. Если установить значение свойства UniDirectionaI равным True, то 
перемещаться по набору данных класса Query можно будет только от начала до 
конца. Значение свойства UniDirectionaI по умолчанию равно False, что 
позволяет перемещаться по набору данных в обоих направлениях. 

    Методы 

    Класс TQuery предоставляет много методов, позволяющих управлять объектами 
во время выполнения. Особый интерес вызывает метод ExecSQL. Он используется 
вместо метода Open, если оператор SQL не должен возвращать результат. Примером 
может служить оператор DELETE, который просто удаляет записи, удовлетворяющие 
определенному критерию, указанному в компоненте WHERE. В следующем примере 
используется метод ExecSQL для выполнения запроса, по которому удаляются все 
записи таблицы TempStorage. 

    with qryEmpty do 
    begin 
    { Удалить текущий запрос SQL. } 
    SQL.Clear; 
    { Добавить оператор. }                                     .'.,.• 


    Глава 9. Работа с локальными базами данных                             263 


    SQL.Add('delete * from TempStorage'); 
    ( Выполнить оператор. } ExecSQL; 
    end; 


    Использование модулей данных 

    Если у вас есть приложение с несколькими формами, то вы, возможно, 
обходитесь несколькими экземплярами каждой таблицы, например таблицей 
заказчиков класса ТТаЫе на экране заказчика и таблицей заказов на экране 
заказов для уточнения деталей, касающихся заказчика. Реализация этой процедуры 
не только отнимает много времени, но и приводит к некоторым проблемам 
сопровождения. Чтобы применять одинаковые бизнес-правила во всем приложении, 
необходимо вызвать глобальную процедуру из каждого обработчика событий, для 
которых требуются общие функциональные возможности. Это решение, очевидно, не 
является оптимальным, но совпадает с тем вопросом, с которого нам необходимо 
начать изучение модулей данных. 
    В модуль данных помещаются невизуальные компоненты для сохранения в 
объекте-контейнере. Этот процесс позволяет создать экземпляр пользовательской 
таблицы, поместить его в модуль данных и использовать в любой части приложения. 

    Создание модуля данных 

    Чтобы создать модуль данных, воспользуйтесь командой File^New Data 
Module. Затем 'i4'f1F'«l^S3BBBEsB перенесите любой невизуальный компонент в 
окно модуля данных, установите его свойства и используйте в любом месте 
приложения. На рис. 9.5 показано окно модуля данных, содержащее объекты 
TTimer и TOpenDialog. 

    OpenOalogI Timeri             || 

    II Рис. 9.5. Используйте модуль данных для объединения и совместного 
использования невизуальных 

    ||||||| Созданный модуль данных можно добавить в хранилище объектов для 
использования другими приложенияsJ ми. Щелкните правой кнопкой мыши в окне 
модуля данных и выберите в контекстном меню команду Add to —•^ Repository. 
Более подробная информация о хранилище объектов приведена в главе 2, 
"Интегрированная среда Delphi". 

    Использование модуля данных 

    Использование модуля данных подобно использованию формы. Пусть, например, 
у нас есть модуль данных dmApp, который содержит объект tbICust класса ТТаЫе. 
Этот модуль сохраняется в модуле DM App. Чтобы использовать модуль данных 
dmApp, нужно включить модуль DM_App в раздел uses вашего модуля. 
    В следующем фрагменте программы выводится сообщение с количеством записей 
в таблице tbICust из модуля данных dmApp. 

    Implementation 
    uses DM_App; 
    procedure DisplayRecordCount; 
    begin with dmApp.tbICust do 
    ShowMessage(Format('Record count = %d',[RecordCount])) ; 
    end; 


    События модуля данных 

    Модуль данных является экземпляром класса TDataModule и поэтому может 
предоставлять свойства, методы и события. Поскольку свойства и события не 
особенно интересны, рассмотрим лишь два события, предоставляемых классом 
TDataModule,— OnCreate и OnDestroy. 
    Событие OnCreate происходит, когда создается модуль данных. Оно 
используется для инициализации таких компонентов в модуле данных, как 
регистрация в удаленной базе данных или установка свойства Filter компонента 
TOpenDialog, основанного на предпочтении пользователя выполнять поиск с начала 
работы приложения. Событие OnDestroy используется для освобождения ресурсов, 
выделенных в событии OnCreate. 

    264                                     Часть III. Работа с базами данных 


    Элементы управления для работы с данными 

    В главах 6, "Использование компонентов Delphi", и 7, "Общие элементы 
управления Win32", описываются элементы управления, которые представляют собой 
компоненты библиотеки визуальных компонентов (VCL), предоставляющей широко 
используемые функции. Вы могли бы использовать эти элементы управления для 
разработки приложений баз данных и написания кода для преобразования данных 
между компонентами доступа к таким данным, как ТТаЫе и TQuery, и вашими 
компонентами интерфейса (такими, "как T^fca^tiiTStringGrid). Это было бы 
исключительнсГутомительно, и вам, наверное, захотелось бы вернуться к Visual 
Basic, чтобы освободиться от кодирования. Хорошая новость заключается в том, 
что в Delphi существует великолепный набор элементов управления для работы с 
данными (data aware components). Этот термин означает, что элемент управления 
может автоматически читать данные из набора данных и выводить их на экран. 
Кроме того, он достаточно интеллектуален, чтобы обновлять данные, когда 
выполнены изменения. Элементы управления для работы с данными располагаются на 
странице Data Controls палитры компонентов. 
    Используя элементы управления для работы с данными, можно создать простую 
программу ввода данных, не написав ни единого фрагмента кода. 

    Класс TDataSource 

    Для реализация связи между интерфейсом набора данных и интерфейсом 
элемента управления в Delphi вводится объект, известный как источник-данных 
(data source). Он служит связующим звеном между набором данных и элементом 
управления, а также имеет несколько интересных свойств и событий. 

    Свойства 

    В классе TDataSource определены следующие свойства, которые позволяют 
работать с экземплярами этого класса. 
    • AutoEdit. Использует логическую переменную, чтобы определить, переведен 
ли автоматически набор данных в режим редактирования, когда вы редактируете 
информацию в элементе управления, связанном с этим источником данных. 
    • DataSet. Указывает набор данных, с которым связан компонент источника 
данных. Свойство DataSet может указывать на класс TQuery или ТТаЫе. 
    • State. Возвращает текущее состояние набора данных. 

    События 

    Класс TDataSource имеет следующие события, сигнализирующие об изменениях в 
источнике данных. 
    • OnDataChange. Возникает, когда одно (или несколько) поле в таблице 
изменилось в результате редактирования отдельного поля или пересылки в новую 
запись. Обработчик событий передает поле, которое было изменено. Если 
приложение переместило указатель к следующей записи, то аргумент поля будет 
Nil, поскольку все поля изменятся автоматически. Событие OnDataChange полезно, 
если нужно обновить содержимое элементов управления, не предназначенных для 
управления данными. 
    • OnStateChange. Происходит, если свойство State источника данных было 
изменено. Событие может возникнуть при переходе набора данных из состояния 
dsBrowse во время просмотра в состояние ds Inactive, когда набор данных 
закрыт. С более подробной информацией о событии OnStateChange и свойстве State 
можно ознакомиться в разделе OnStateChange, TDataSource электронной справки. 
    • OnUpdateData. Является дополнением к событию OnDataChange. Событие 
OnDataChange уведомляет приложение о том, что необходимо читать измененную 
информацию из набора данных, а событие OnUpdateData дает возможность 
записывать данные из элементов управления, не предназначенных для управления 
данными, в набор данных. 

    Краткая характеристика элементов управления для работы с данными 

    Более или менее полная информация об элементах управления для работы с 
данными требует отдельной главы. Тем не менее в большинстве случаев лишь два 
следующих свойства играют решающую роль при работе с данными. 
    • DataSource. Указывает компонент TDataSource, с которым связан элемент 
управления для работы с данными. 
    • DataField. Определяет поле, лежащее в основе элемента управления для 
работы с данными. 

    Глава 9. Работа с локальными базами данных                             265 

    Все остальные свойства элементов управления вы можете изучить 
самостоятельно. В табл. 9.3 приведено краткое описание каждого элемента управления. 

    Таблица 9.3. Элементы управления для работы с данными 

    элемент управления	иписание 

    TDBChart	Позволяет представлять информацию в графическом виде, почти не 
требует кодирования с вашей стороны 
    TDBGrid	Отображает набор данных ТТаЫе или TQuery. Кроме того, позволяет 
вставлять, редактировать и удалять записи 
    TDBNavigator	Позволяет перемещаться по набору данных и вставлять, 
редактировать или удалять записи, а также регистрировать или отменять изменения 
    TDBText	Подобно элементу управления TLabel, выводит текст только для чтения 
    TDBEdit	По сравнению с элементом TEdit, позволяет редактировать отдельные 
строки текста. Его использование не ограничивается только текстовыми полями: с 
его помощью можно проверить достоверность входных данных и других типов, таких 
как цифровые или даты 
    TDBMemo	Расширенная версия TDBEdit. Позволяет редактировать несколько 
строк текста и обычно применяется для полей примечаний 
    TDBImage	Выводит образ поля BLOB или графическое поле. Можно поместить 
новое изображение в поле путем его вставки из буфера обмена 
    TDBListBox	Представляет прокручиваемый список значений 
    TDBComboBox	Подобно элементу TDBListBox, позволяет выбрать отдельное 
значение из раскрывающегося списка 
    TDBCheckBox	Как и элемент управления TCheckBox, позволяет выполнять 
переключение (on/off). Хотя этот элемент управления обычно используется для 
сохранения значений Yes/No или True/False, можно самостоятельно выбрать 
соответствующие значения состояний 
    TDBRadioGrouop	Этим элементом можно воспользоваться, если в поле может 
содержаться более двух различных значений 
    TDBLookupListBox	Расширенная версия элемента TDBListBox. При использовании 
TDBLookupListBox элементы списка берутся из другого набора данных, а не 
перечисляются явно в массиве свойств TStrings, как для элемента TDBListBox 
    TDBLookupComboBox	Это TDBLookupListBox, имеющей форму и функциональные 
возможности элемента TCornboBox 
    TDBRichEdit	Это элемент управления для работы с данными, эквивалентный по 
функциональным возможностям элементу TRichEdit 
    

    Усовершенствованные методы программирования баз данных 

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

    Кэширование изменений 

    Кэширование изменений полезно при одновременном обновлении большого числа 
записей (пакета). Записи кэшируются с помощью BDE и затем по запросу 
записываются за одно действие. Таким образом, скорость выполнения операций с 
пакетами значительно увеличивается. 

    266                                     Часть IIJ. Работа с базами данных 

    Управление кэшированием изменений осуществляется с помощью свойства 
CachedUpdates и метода ApplyUpdates. Если установить значение CachedUpdates 
равным True, записи будут кэшироваться, а при вызове метода АрplyUpdates 
кэшированные записи будут физически обновлены в таблице. 

    ПРЕДОСТЕРЕЖЕНИЕ 
    Если установить значение CachedUpdates равным False, то все кэшированные, 
но незаписанные записи будут утеряны. 
    В следующем фрагменте программы кэширование изменений используется для 
обновления 50 записей таблицы. 

    var 
    iCount : Integer; 
    begin 
    with tbCust do 
    begin 
    { Переходим в режим кэширования изменений. } CachedUpdates := True; 
    { Добавляем 50 записей в таблицу. } for iCount := 1 to 50 do 
InsertRecord([iCount]) ; 
    { Обновляем кэшированные записи в таблице. } ApplyUpdates; 
    end; 
    end; 


    Исключительные ситуации баз данных 

    Обработка исключительных ситуаций рассматривалась в главе 5, "Обработка 
исключительных ситуаций". Кроме описанных классов, в Delphi определено также 
два класса исключительных ситуаций, связанных с программированием баз данных. 
    • EDatabaseError. Возникает, если компонент VCL обнаружил ошибку в базе 
данных. Исключительная ситуация отображает простое текстовое сообщение с 
описанием возникшей проблемы и подобна любой другой исключительной ситуации, 
например EDivByZero или EAccessViolation. 
    В EDBEngineError. Генерируется, если ошибка возникла в системе BDE. 
Поскольку исключительная ситуация может произойти из-за нескольких ошибок, 
класс EDBEngineError определяет два дополнительных свойства — ErrorCount и 
Errors. В следующем фрагменте программы добавляется список сообщений объекта 
EDBEngineError к элементу управления TListBox для отображения. 

    var iCount : Integer; 
    begin 
    try 
    { Что-нибудь. } 
    except 
    ( Проверить тип сгенерированного исключения. } 
    on E:EDBEngineError do 
    ( Перебор всего массива ошибок. } 
    for iCount := 0 to E.ErrorCount-1 do 
    { Добавляем текст ошибки в окно списка. } 
    IbMsgs.Items.Add(E.Errors[iCount].Message); 
    end; 
    end; 


    Глава 9. Работа с локальными базами данных                             267 


    Транзакции 

    Обработка транзакций используется, если для базы данных критична 
целостность данных. Транзакция базы данных — это просто группа операций с 
несколькими наборами данных. Каждая из этих операций должна быть успешно 
выполнена до того, как транзакция всей базы данных будет считаться успешной. 
Если операция дала сбой, транзакция базы данных позволяет программе 
возвратиться назад через все предыдущие операции и оставить базу данных в 
первоначальном состоянии. 
    Логика этой процедуры быстро усложняется, особенно если вы решите, что 
другие пользователи могут создавать транзакции одновременно с вами. К счастью, 
система BDE может выполнять эту отвратительную работу вместо вас, используя 
три метода класса TDatabase — StartTransaction, Commit и Rollback. 
    Метод StartTransaction инициирует запись транзакции. Все операции, такие 
как удаление, вставка и редактирование, записываются. После окончания 
обработки вызывается метод Commit для записи всех изменений в базу данных. 
Если вы обнаружили ошибку, вызывайте метод Rollback для отмены всех изменений 
в базе данных. В следующем фрагменте программы реализуется простое обновление 
счета для главной бухгалтерской книги. Здесь используется объект dbGL класса TDatabase. 

    dblTranVal := 100; 
    { Запуск транзакции. } 
    dbGL.StartTransaction; 
    try 
    { Найти банковский счет. } 
    if not tblGLAcc.FindKey([400.100']) then 
    raise Exception.Create('Unable to locate bank account'); 
    { Модифицировать баланс. } 
    tbIGLAcc.Edit; 
    tbIGLAcc.FieldByName('Balance').AsFloat := 
    tbIGLAcc.FieldByName('Balance').AsFloat - dblTranval; 
    tbIGLAcc.Post; 
    ( Записать изменения. } dbGL.Commit; 
    except 
    { Отмена изменений. } 
    tbIGL.Rollback; 
    { Вызвать исключительную ситуацию. } 
    raise; 
    end; 

    Обратите внимание на использование обработчика исключительных ситуаций для 
прерывания любой исключительной ситуации и отмены транзакции. 

    Класс TSession и подзадачи в операциях с базами данных 

    С появлением системы Win32 пользователь без проблем может писать настоящие 
многозадачные приложения. Многозадачные приложения появились одновременно с 
другими возможностями, такими как проверка орфографии в документе во время 
печати другого документа. В главе 24, "Работа с подзадачами", детально 
рассматривается программирование подзадач, но нам нужно разобраться, как 
влияет использование подзадач на операции с базами данных. 
    Подобно любым другим компонентам, компоненты доступа к данным не защищены 
при совместном использовании несколькими подзадачами. Если вы попытаетесь 
использовать отдельный объект TTable в нескольких подзадачах, можете 
сгенерировать исключительную ситуацию нарушения доступа, испортить таблицу, 
считать или записать неверные данные, а также сделать массу других 
нежелательных действий с базой данных. 
    К счастью, библиотека VCL Delphi предоставляет несколько классов, которые 
делают подзадачи в операциях с базами данных совершенно безопасными. 

    268                                     Часть III. Работа с базами данных 


    Классы Session, TSession и TSessionList 

    Система BDE поддерживает работу сразу нескольких приложений — вы можете 
запустить систему Database Desktop одновременно с Delphi. Эти возможности 
поддерживаются на основе понятия сессий (session). Это просто уникальное 
соединение с базой данных, используемое приложением или задачей. 
    Класс TSession инкапсулирует функции сессии, которые экземпляр, называемый 
Session, автоматически создает во время выполнения и разрушает при завершении 
программы. Объект Session представляет собой задаваемую по умолчанию сессию 
для использования главной задачей вашего приложения. Среди множества методов и 
свойств класса TSession имеет большое значение свойство SessionName — оно 
должно содержать уникальную строку, определяющую сессию базы данных. 
    В классе TSessionList содержится список всех активных BDE-сессий. Вам 
никогда не придется создавать экземпляр класса TSessionList, так как Delphi 
автоматически создает переменную сессии во время выполнения и освобождает ее 
после завершения программы. 

    Создание сессии 

    Сессия создается с помощью метода OpenSession класса TSessionList. Методу 
OpenSession передается в качестве параметра уникальное имя сессии 
(SessionName). По этому имени осуществляется поиск глобальной переменной 
сессии Sessions для сравнения с объектом TSession. Если не находится 
совпадающего, метод OpenSession создает и возвращает новый экземпляр объекта 
TSession. В следующем фрагменте программы создается и закрывается сессия 

    TestSession. 
    var ssnTemp : TSession; 
    begin 
    { Открыть сессию. } 
    ssnTemp := Sessions.OpenSession('TestSession'); 
    { Закрыть сессию. } 
    ssnTemp.Free; 
    end; 


    Использование новой сессии 

    После создания сессии необходимо создать компоненты доступа к базе данных 
и соединить их с сессией. Компоненты доступа к базе (ТТаЫе, TQuery и 
TStoredProc) обладают свойством SessionName, которое явно установлено равным 
свойству SessionName экземпляра класса TSession, возвращаемого методом OpenSession. 
    Вот и все, что вам нужно знать для создания многозадачных^приложений баз 
данных. Код в листинге 9.1 ^ ^ftig^^, взят и3 программы UTESTTHREAD.PAS, 
находящейся'^ дополнительно распространяемой дискете. 

    i Листинг 9.1. \UDELPHI3\CHP9\UTESTTHREAD. PAS. Пример процесса для работы 
с базой данных 

    procedure TTestThread.Execute; 
    var 
    ssnLocal : TSession; 
    tbITest : TTable; 
    begin 
    ( Устанавливаем самый низкий приоритет. } 
    Priority := tpLowest; 
    try 
    { Создаем уникальный объект сессии. } 
    ssnLocal :=Sessions.OpenSession(ExtractFileName 
(Application.EXEName)+IntToStr(GetTickCount)) ; 
    ( Создаем объект локальной таблицы. } 
    tbITest := TTable.Create (Nil); 


    Глава 9. Работа с локальными базами данных                             269 


    with tbITest do 
    begin 
    ( Связываем эту таблицу с новой сессией. } 
    SessionName     := ssnLocal.SessionName; 
    { Инициализируем свойства таблицы: базу данных и имя. } 
    DatabaseName    := 'DBDEMOS'; 
    TableName       := 'Items'; 
    { Открываем таблицу. } 
    Open; 
    end; 
    { Выполнять цикл, пока поток не будет завершен. } 
    while not Terminated do 
    ( Вставьте сюда ваш потоковый код. } 
    finally 
    { Закрываем таблицу. ) 
    tbITest.Free; 
    { Закрываем сессию. ) 
    ssnLocal.Free; 
    end; 
    end; 


    Резюме 

    В этой главе довольно много теории. Сначала рассматривались понятия базы 
данных, таблицы, записи и поля. Вы наверняка помните, что база данных — это 
набор таблиц, а таблица — это просто сетка, столбцы которой являются полями, а 
строки — записями. 
    После определения основных понятий были описаны типы баз данных, 
поддерживаемых BDE (Paradox и dBASE), a также преимущества, недостатки и 
особенности каждого из форматов. 
    Затем были рассмотрены компоненты доступа к данным из библиотеки VCL, а 
также общие и специфические методы и свойства для классов TTable и TQuery. 
    После этого речь шла о централизации компонентов доступа к данным с 
помощью класса TDataModule и о способах создания и использования модуля данных. 
    Кроме того, вы узнали об установке предоставляемых библиотекой VCL 
элементов управления для работы с данными. Вместо связи непосредственно с 
набором данных эти элементы управления используют компонент класса TDataSource 
в качестве канала данных. Было описано большинство полезных свойств и событий 
класса TDataSource и представлена таблица с информацией о каждом элементе 
управления, распознающем данные. 
    В конце концов мы привели вас к краю пропасти и бросили в пучину 
усложненных подходов в деле программирования баз данных. Были рассмотрены 
кэшируемые изменения, исключительные ситуации для баз данных, транзакции и 
многозадачные операции в базах данных. 

    270                                      Часть III. Работа с базами данных 
