Источники данных
Как уже было сказано, невидимая компонента TDataSource действует как интерфейс между некоторым объектом набора данных (таблица, запрос) и визуальной компонентой управления. С одной стороны, все наборы данных должны быть ассоциированы с некоторым источником. С другой стороны, каждая компонента управления должна быть ассоциирована с источником, чтобы получать данные для отображения и редактирования. Каждой компоненте набора данных должна соответствовать по меньшей мере одна компонента источника. Далее мы увидим, что все компоненты управления имеют свойство DataSource, значение которого замыкает трехступенчатую связь. Рис. 5.6 показывает свойства компоненты источника в окне Инспектора объектов:
Рис. 5.6. Свойства источники.
AutoEdit разрешает или запрещает режим редактирования записей, вводимых в поля компонент управления. Значение true включает режим редактирования по умолчанию.
DataSet определяет имя конкретного набора данных (таблицы или запроса), который питает данный источник. Можно переключаться с одного набора данных на другой "на лету", во время выполнения программы. Следующий простейший код реализует попеременное подключение объекта источника DataSourcel к таблице заказчиков "Заказчики" или к таблице "Заказы":
if (DataSourcel->DataSet == "Заказчики")
DataSourcel->DataSet = "Заказы";
else
DataSourcel->DataSet = "Заказчики";
Чтобы синхронизировать работу компонент управления на двух формах, достаточно установить свойство DataSet на один и тот же набор данных:
void_fastcall TForm2::FormCreate (TObject *Sender)
{
DataSourcel->Dataset = Forml->Tablel;
Name определяет содержательное название данной компоненты, отличающее ее от других источников данных вашего приложения. Целесообразно давать источнику имя. которое отражает название ассоциированного с ним набора данных. Например, если вы собираетесь работать с таблицей клиентов "Clients", заданной свойством DataSet, то пусть значением свойства Name будет "ClientsSource" или подобное имя.
С компонентой TDataSource связаны три события:
• OnDdlciC/iange возникает при перемещении курсора на новую запись и используется при необходимости "ручной" синхронизации поведения компонент управления.
• OnStciteChcinse возникает при изменении свойства State наборов данных. Например, обработчик события (Листинг 5.1) будет отслеживать изменения состояния таблицы MyTable, выводя па форму соответствующие текстовые отметки.
void_fastcall TFormI::StateChange(TObject *Sender)
{
char S[20];
switch (MyTabie->State) {
case dslnactive:
strcpy(S,"Таблица неактивна");
break;
case dsBrowse:
strcpytS, "Идет просмотр");
break;
case dsEdit:
strcpytS, "Идет редактирование");
break;
case dslnsert:
strcpy(S, "Идет вставка записи");
break;
} // Вывод текстовой строки S
}
Листинг 5.1. Обработчик события OnStateChange источника.
• OnUpdateData возникает перед обновлением текущей записи и используется при необходимости синхронизации поведения обычных компонент с изменениями некоторого набора данных.
5.2.1.2 Таблицы
Компонента таблицы представляет собой один из самых фундаментальных и гибких компонентных классов. TTable устанавливает прямую связь с таблицей базы данных посредством BDE, причем все записи или столбцы этой таблицы становятся доступными для приложения - как раздельно, так и внутри определенного интервала адресов. Рис. 5.7 показывает свойства компоненты таблицы в окне Инспектора объектов:
Рис. 5.7. Свойства таблицы. Active разрешает или запрещает режим просмотра "живых данных" таблицы на этапе проектирования. Значение true или метод Open открывают просмотр таблицы. Значение false (устанавливается по умолчанию) или метод Close закрывают просмотр.
DatabaseName содержит псевдоним базы данных или полный путь к ее каталогу. Использование псевдонима всегда предпочтительнее: вы можете переназначить физический носитель данных, например, заменив локальный дисковод на сетевой. Перекомпиляция приложения не требуется - просто измените путь на вкладке Aliases в утилите конфигурации BDE.
TableName позволяет выбрать фактическое имя таблицы из выпадающего списка с именами всех таблиц в адресуемой базе данных.
Exclusive разрешает или запрещает другому приложению обращаться к таблице, пока вы ее используете сами. Значение false запрещает исключительный доступ по умолчанию.
IndexFiles открывает диалог выбора индексного файла для таблицы.
IndexName задает правило отображаемых сортировки данных, отличное от упорядочивания по первичному ключу (primary key order).
Filter позволяет устанавливать критерий фильтрации, в соответствии с которым адресуется некоторое подмножество записей таблицы.
ReadOnly управляет правами доступа в процессе выполнения программы. Значение false разрешает запись по умолчанию.
MasterFields и MasterSource участвуют в образовании связи двух таблиц (ведущей и ведомой) по принципу master-detail. Следующая процедура иллюстрирует процесс создания простой формы для демонстрационной базы данных BCDEMOS, в которой пользователь может прокручивать записи таблицы заказчиков CUSTOMER с отображением всех заказов таблицы ORDERS, сделанных текущим заказчиком:дексного файла для таблицы.
IndexName задает правило отображаемых сортировки данных, отличное от упорядочивания по первичному ключу (primary key order).
Filter позволяет устанавливать критерий фильтрации, в соответствии с которым адресуется некоторое подмножество записей таблицы.
ReadOnly управляет правами доступа в процессе выполнения программы. Значение false разрешает запись по умолчанию.
MasterFields и MasterSource участвуют в образовании связи двух таблиц (ведущей и ведомой) по принципу master-detail. Следующая процедура иллюстрирует процесс создания простой формы для демонстрационной базы данных BCDEMOS, в которой пользователь может прокручивать записи таблицы заказчиков CUSTOMER с отображением всех заказов таблицы ORDERS, сделанных текущим заказчиком:
1. Выполните команду главного меню File | New Data Module, чтобы открыть контейнер нового модуля данных DataModule2. В этот модуль поместите две пары компонент доступа к базам данных TTable и TDataSource (Рис. 5.8).
2. Установите свойства объекта ведущей таблицы Tablet
DatabaseName = BCDEMOS TableName = CUSTOMER.DB Name = CustomersTable
Рис. 5.8. Модуль данных.
3. Установите свойства объекта ведомой таблицы Table2
DatabaseName = BCDEMOS TableName = ORDERS.DB Name = OrdersTable
4. Установите свойства объекта источника DataSourcel DataSet = CustomersTable Name = CustomersSource
5. Установите свойства объекта источника DataSource2 DataSet = OrdersTable Name = OrdersSource
6. Поместите на форму пару компонент управления сеткой TDBGrid.
7. Выполните команду File | Include Unit Hdr, чтобы указать, что данная форма должна использовать созданный модуль данных.
8. Установите свойство объекта первой сетки DBGridI
DataSource = DataModule2->CustomersSource и свойство объекта второй сетки DBGrid2
DataSource = DataModule2->OrdersSource
9. Активизируйте таблицу OrdersTable модуля данных и установите свойство
MasterSource = CustomersSource, чтобы связать ведущую таблицу CUSTOMER с ведомой таблицей ORDERS.
10. Дважды щелкните мышью в графе значений свойства MasterFields, и в открывшемся окне дизайнера связи полей
- выберите номер заказчика CustNo (связующее поле таблиц) из выпадающего списка Available Indexes;
- задайте CustNo в списках Detail Fields и Master Fields;
- нажмите кнопку Add, чтобы добавить в список Joined Fields соединение CustNo -> CustNo;
- нажмите кнопку OK, подтверждая сделанный выбор.
11. Установите свойство Active = true для таблиц CustomersTable и OrdersTable с тем, чтобы сразу же отобразить живые данные в сетках на форме.
12. Скомпилируйте и запустите приложение. Рис. 5.9 показывает работу связанных таблиц: при прокрутке записей таблицы заказчиков вы увидите только те записи в таблице заказов, которые относятся к текущему заказчику.
Рис. 5.9. Связь таблич по принципу muster-detail.
Методы Locate и Lookup используются для поиска указанных записей как в индексных таблицах, так в таблицах с ключами: эти методы реализуют самый быстрый из возможных способов поиска в данной таблице. Если столбцы для поиска индексированы и индекс совместим с указанными опциями, используется способ индексного поиска. В противном случае методы создают для BDE соответствующий фильтр.
Locate производит поиск специфической записи и позиционирует курсор на нее. В простейшем варианте вы передаете методу название столбца для поиска, искомое значение ключа записи и флаг опций. Листинг 5.2 содержит фрагмент кода, обеспечивающего поиск в столбце "Имя" таблицы MyTable первой записи со значением "Иван". Если поиск завершился успешно. Locate возвращает значение true, и найденная запись становится текущей. Если искомая запись не найдена. Locate возвращает значение false, и позиция курсора не меняется.
( boot Success;
TLocateOptions Options;
Options “ loPartialKey;
Success = MyTable->Locate("Имя", "Иван", Options);
}
Листинг 5.2. Простейший пример использования метода Locate.
Возможности метода проявляются при поиске вариантных значений записи в нескольких столбцах таблицы. Обобщенный синтаксис описания метода имеет следующий вид:
bool _fastcall Locate(const AnsiString KeyFields, const Variant SKeyValues, TLocateOptions Options);
В главе 3 отмечалось, что различные типы искомых значений объявляются в шаблонном классе Variant (с помощью которого C++Builder реализует одноименный встроенный тип Delphi). Названия столбцов для поиска разделяются в текстовой строке параметра KeyFields символом точка с запятой.
Lookup выбирает значения столбца той записи, которая удовлетворяет заданным значениям поиска. Позиция курсора не меняется. В простейшем варианте вы передаете методу название столбца для поиска, искомое значение ключа записи и возвращаемые поля этой записи. Листинг 5.3 содержит фрагмент кода, обеспечивающего поиск в таблице MyTable первой записи, у которой в столбце "Фирма" имеется значение "Borland". Если поиск завершился успешно. Lookup возвращает в массиве типа Variant название фирмы, фамилию ее представителя и номер телефона. Если искомая запись не найдена. Lookup возвращает значение Null.
{
Variant Results;
Results = MyTable->Lookup("Фирма", "Borland",
"Фирма;Представитель;Телефон") ;
}
Листинг 5.3. Простейший пример использования метода Lookup.
Возможности метода проявляются при поиске вариантных значений полей записи в нескольких столбцах таблицы. Обобщенный синтаксис описания метода имеет следующий вид:
Variant _fastcall Lookup(const AnsiString KeyFields, const Variant sKeyValues, const AnsiString ResultFields);
Названия столбцов для поиска разделяются в текстовой строке параметра KeyFields символом точка с запятой.
С компонентой TTable также связаны следующие методы:
• GotoCurrent синхронизирует перемещения курсора по нескольким табличным компонентам, ассоциированным с одной и той же фактической таблицей базы данных.
• First, Next, Prior, Last и MoveBy используются для навигации по данным таблицы.
• SetKey, FindKey, FindNearest, GotoKey и GotoNearest используются для поиска по специфическим значениям ключей.
• Append, Insert, AppendRecord и InsertRecord добавляют новую запись к таблице. Delete вычеркивает текущую запись. Edit разрешает приложению модифицировать записи, a Post вызывает фактическое изменение содержимого базы данных.
• EditRangeStart, EditRangeEnd, SetRangeStart, SetRangeEnd, ApplyRange и SetRange устанавливают границы интервала адресов записей, возвращаемых приложению при групповом доступе.
5.2.1.3 Запросы
Компоненты таблиц являются полноправными, гибкими и легкими в использовании компонентами доступа, достаточными для многих приложении СУБД. TTable возвращает все строки и столбцы единственной таблицы, если доступ не ограничивается установкой интервалов и фильтров. Компоненты запросов предоставляют разработчикам альтернативные возможности. TQuery обеспечивает доступ к нескольким таблицам одновременно и способна адресовать некоторое подмножество записей. Вид возвращаемого набора данных (result set) зависит от формы запроса, который может быть либо статическим, когда все параметры запроса задаются на стадии проектирования, или динамическим, когда параметры определяются во время выполнения программы.
Указанные действия записываются и реализуются на стандартизованном языке структурированных запросов SQL (Structured Query Language), принятом большинством удаленных серверов реляционных баз данных, таких как Sybase, Oracle, InterBase и SQL Server. Ha SQL можно сформулировать весьма изощренные запросы к базам данных. C++Builder передает запросы серверу, который интерпретирует их и возвращает результаты вашему приложению.
Рис. 5.10 показывает свойства компоненты запроса в окне Инспектора объектов:
Рис. 5.10. Свойства запроса. Active разрешает или запрещает режим просмотра "живых данных", возвращаемых запросом на этапе проектирования. Значение false устанавливается по умолчанию.
DatabaseName содержит псевдоним базы данных или полный путь к ее каталогу, необходимые для разрешения запроса.
RequestLive разрешает или запрещает BDE сделать попытку возвратить "живой" результирующий набор. Значение false (устанавливается по умолчанию) указывает, что результаты запроса нельзя модифицировать. Значение true гарантирует возврат результирующего набора при условии, что синтаксис команды SELECT согласуется с требованиями запрашиваемых данных.
SQL используется для ввода команды SQL посредством строчного редактора списка, который открывается двойным щелчком мышью в графе значений этого свойства. Локальные и удаленные серверы баз данных обеспечивают выполнение четырех команд SQL: SELECT - для выбора существующих данных из таблиц; INSERT - для добавления
новых данных в таблицы; UPDATE - для модификации данных таблиц; DELETE -для удаления данных из таблиц. Результаты обработки запроса возвращаются приложению клиента. Следующая процедура иллюстрирует процесс создания формы со статическим запросом к таблице EMPLOYEE всей информации о служащих, зарплата которых превышает заданную величину:
1. Поместите компоненту TQuery на форму.
2. Установите псевдоним адресуемой базы данных сервера в свойстве DatabaseName. В примере используется псевдоним BCDEMOS локальной демонстрационный базы данных, содержащей, в частности, таблицу служащих некоторого предприятия.
3. Откройте строчный редактор списка, введите команду SQL
SELECT * FROM EMPLOYEE WHERE Salary>40000 и нажмите кнопку ОК.
4. Поместите на форму компоненту TDataSource и установите ее свойство DataSet = Queryl.
5. Поместите на форму компоненту управления сеткой TDBGrid и установите ее свойство DataSource = DataSourcel.
6. Установите свойство Active = true для запроса Queryl с тем, чтобы сразу же отобразить живые данные в сетке (Рис. 5.11).
Рис. 5.] ]. Форма приложения со статическим запросом к таблице служащих.
Свойство SQL имеет объектный тип TStrings и заключает список текстовых строк наподобие массива. Листинг 5.4 показывает обработчик события ButtonlClick, реализующий ввод запроса пользователем при нажатии кнопки на форме. Введенная команда SQL записывается в строчный массив (того же типа TStrings) свойства Memol->Lines компоненты редактирования TMemo. Результаты запроса можно, как и в предыдущем примере, отобразить на сетке.
void_fastcall TFormI::ButtonlClick(TObject *Sender) {
// Проверить, введена ли какая-то строка в Memol if (strcmp(Memol->Lines->Strings[0].c_str(), "") == 0) (
MessageBox(0, "No SQL Statement Entered", "Error", MB_OK) ;
return;
} else
(
// Деактивировать предыдущий запрос, если он имел место Queryl->Close ();
// Очистить свойство SQL от предыдущего запроса Queryl->SQL->Clear () ;
// Присвоить введенный в Memol текст свойству SQL Queryl->SQL->Add(Memol->Lines->Strings[0].c_str()) ;
try
{
Queryl->0pen(); // выполнить команду SQI }
catch(EDBEngineError* dbError) .// обработка ошибок BDE {
for (int i=0; i<dbError->ErrorCount; i++) MessageBox (0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ;
} ) }
Листинг 5.4. Ввод и исполнение команды SQL.
Params позволяет специфицировать имена, типы и начальные значения параметров запроса. C++Builder дает возможность конструировать команду SQL динамического запроса с параметрами. Чтобы указать нужный параметр динамического запроса, используйте символ двоеточия перед именем этого параметра. Например, параметр номера служащего в таблице employee идентифицируется следующей командой SQL: SELECT * FROM employee WHERE EmpNo = :EmpNo.
Увидеть или поменять атрибуты выбранного параметра можно посредством диалогового редактора (Рис. 5.12), который открывается двойным щелчком мышью в графе значений этого свойства. Нажатие кнопки ОК подготавливает SQL сервер к запросу и вызывает попытку его выполнения на стадии проектирования приложения.
Рис. 5.12. Редактор параметров запроса.
Свойство Params содержи-. указатель на объект типа TParams. Поэтому изменить значение параметра во время выполнения программы можно по ^ндексу в массиве I terns объекта типа TParams: ;
Queryl->Params->Items[0]->AsInteger = 4;
или по имени параметра, посредством метода ParamByName:
Queryl->ParamFjyName ("FirstName") ->AsString = "John";
Листинг 5.5 дает законченный пример использования метода ParamByName во время исполнения программы. Параметр имени служащего FirstName идентифицируется следующей командой SQL:
SELECT * FROM EMPLOYEE WHERE FirstName = :FirstName
Заметим, что обработчик события первым делом обращается к методу подготовки запроса Prepare, посылая команду SQL серверу. Сервер выделяет ресурсы и оптимизирует динамический запрос только однажды, до первого исполнения. Все, что необходимо теперь, - это подставлять новые значения параметров и выполнять команду запроса с помощью метода Open.
void_fastcall TFormI::ButtonlClick(TObject *Sender) {
// Деактивировать предыдущий запрос, если он имел место Queryl->Close () ;
if (!Queryl->Prepared)
Queryl->Prepare(); // подготовить запрос // Заменить значение параметра на введенное пользователем Queryl->ParamByName("FirstName")->AsString =
Editl->Text.c_str() ;
try
{
Query 1 ->0pen(); // выполнить команду SQL }
catch(EDBEngineError* dbError) // обработка ошибок BDE {
for (int i=0; i<dbError->ErrorCount; i++)
MessageBox(0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ;
} }
Листинг 5.5. Изменение значения параметра динамического запроса.
Рис. 5.13 показывает работу предыдущего примера: пользователь вводит значение параметра FirstName и в результате выполнения запроса получает список всех служащих с указанным именем.
Рис. 5.13. Результат выполнения динамического запроса с параметром.
DataSource указывает на источник другого набора данных, отличный от источника данного запроса, из которого вам надо выбрать значения текущего поля. Объект запроса будет сравнивать имя параметра в команде SQL с именами полей дочернего набора данных. Когда имена совпадают, параметр автоматически приобретает значение соответствующего поля. Следующая процедура иллюстрирует процесс создания формы, в которой запрос к таблице заказчиков CUSTOMER соединяется с таблицей заказов ORDERS по номеру заказчика CustNo:
1. Поместите компоненты TQuery и TTable на форму и установите псевдоним
BCDEMOS в их свойствах DatabaseName.
void _fastcall TPormI::DataSource2DataChange(TObject
*Sender, TField *Field) {
// Связь запроса с таблицей ORDERS установлена? if (Queryl->DataSource != NULL)
return; // да, возврат // Деактивировать предыдущий запрос, если он имел место Queryl->Close() ;
if (!Queryl->Prepared)
Queryl->Prepare(); // подготовить запрос // Выбрать значение параметра запроса из поля таблицы Queryl->ParamByName("CustNo")->AsInteger =
Tablel->Fields[1]->AsInteger;
try
{
Query 1 ->0pen(); // выполнить команду SQL }
catch(EDBEngineError* dbError) // обработка ошибок BDE (
for (int i=0; i<dbError->ErrorCount; i++)
MessageBox(0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ;
} }
Листинг 5.6. Соединение запроса с таблицей по событию источника.
Для динамического формирования текста командной строки SQL во время исполнения программы удобно использовать стандартную функцию Windows sprintf. Эта функция замещает параметры форматирования (%s, %d, %n и т.д.) передаваемыми значениями, например, в результате подстановки значений параметров форматирования:
tbIName = "EMPLOYEE";
fIdName = "EmpNo";
fIdValue = 3;
sprintf(sqls, "SELECT * FROM %s WHERE %s = %d", tbIName, fIdName, fIdValue)
символьный массив sqls будет содержать следующую команду:
"SELECT * FROM EMPLOYEE WHERE EmpNo =3"
Листинг 5.7 иллюстрирует применение функции sprintf для формирования команды SELECT динамического запроса к таблице EMPLOYEE. Методы Clear и Add используются для занесения этой команды в свойство SQL. Поскольку подготовленный запрос использует ресурсы сервера и нет никакой гарантии, что новый запрос будет работать с данными одной и той же таблицы, C++Builder снимает готовность при любом изменении свойства SQL (т.е. устанавливает значение false свойства Prepared). При очередном исполнении запроса готовность автоматически восстанавливается.
void _fastcall TFormI::ButtonlClick(TObject *Sender)
{
char sqls[250]; // массив для хранения команды SQL
char fmts[50]; // массив для значения зарплаты
// Присвоить fmts значение, введенное пользователем
if ( i (strcmp(SalaryEdit->Text .c_str () , " ") ==0)) strcpytfmts, SalaryEdit->Text.c_str()) ;
else
strcpy(fmts, "100000"); // максимальная зарплата
// Деактивировать предыдущий запрос, если он имел место
Queryl->Close() ;
// Очистить свойство SQL от предыдущего запроса
Queryl->SQL->Clear() ;
// Построить команду SELECT с помощью функции sprintf
sprintf(sqls, "SELECT * FROM EMPLOYEE WHERE Salary<%s", fmts);
// Присвоить сформированную команду SELECT свойству SQL
Queryl->SOL->Add(sqls);
try
{
Query 1 ->0pen(); // выполнить команду SELECT )
catch(EDBEngineError* dbError) // обработка ошибок BDE {
for (int i=0; i<dbError->ErrorCount; i++) MessageBox(0, dbError[i].Message.c_str(), "SQL Error",
MB_OK);, ) 1
Листинг 5.7. Формирование команды SQL с помощью функции sprintf.
Рис. 5.15 показывает работу предыдущего примера: в результате выполнения сформированного запроса пользователь получает список всех служащих, зарплата которых (выбранная из столбца Salary таблицы) оказывается меньше введенного значения.
Рис. 5.15. Результат выполнения сформированного динамического запроса.
Методы Open и ExecSQL предназначены для передачи серверу команды SQL для исполнения. В предыдущих примерах все запросы выдавали единственную команду SELECT. Результат запроса по команде select рассматривается как набор данных, точно так же, как при работе с таблицей. Существуют другие команды SQL, например, команда UPDATE, которая обновляет содержимое некоторой записи, но не возвращает какой бы то ни было результат. Для исполнения сервером таких запросов следует использовать метод ExecSQL вместо метода Open.
Листинг 5.8 представляет собой некоторое обобщение всех рассмотренных ранее операций с динамическими запросами и их параметрами.
Внимание: это приложение предназначено для управляемой модификации столбца Salary таблицы служащих EMPLOYEE из демонстрационной базы данных BCDEMOS. Изменения, внесенные командой UPDATE, необратимы, поэтому перед запуском собранного приложения позаботьтесь о том, чтобы сохранить копию оригинальной таблицы. По умолчанию С-н-Builder помещает все таблицы в каталоге инсталляции \.. .\CBuilder\Examples\Data.
void_fastcall TFormI::ButtonlClick(TObject *Sender) { •
char sqls[250]; // массив для хранения команды SELECT char fmts[50]; // массив для зарплаты и надбавки int mins, adds; // десятичные эквиваленты
// Присвоить fmts зарплату, введенную пользователем
strcpy(fmts, SalaryEdit->Text.c_str());
mins = atoi(fmts);
// Деактивировать предыдущий запрос и очистить SQL
Queryl->Close О ;
Queryl->SQL->Clear () ;
sprintf(sqls, "SELECT * FROM EMPLOYEE WHERE Salary<%s", fmts);
// Присвоить сформированную команду SELECT свойству SQL
Queryl->SQL->Add(sqls) ;
try
{
Queryl->0pen (); // выполнить команду SELECT
} catch(EDBEngineError* dbError) // обработка ошибок BDE
{
for (int i=0; i<dbErro-r->ErrorCount; i++) MessageBox(0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ;
}
// Присвоить fmts надбавку [в %], введенную пользователем
strcpy(fmts, AddEdit->Text.c_str());
adds = atoi(fmts);
// Деактивировать предыдущий запрос и очистить SQL Queryl->Close () ;
Queryl->SQL->Clear() ;
// Присвоить команду UPDATE свойству SQL Queryl->SQL->Add("UPDATE EMPLOYEE set Salary =
(Salary+((Salary*:adds))/100) WHERE (Salary < :mins)");
Queryl->ParamByName("mins")->AsInteger = mins;
Queryl->ParamByName("adds")->AsInteger = adds;
try
{ Queryl->ExecSQL(); // выполнить команду UPDATE
}
catch(EDBEngineError* dbError) // обработка ошибок BDE
{
for (int i=0; i<dbError->ErrorCount; i++) MessageBox(0, dbError[i].Message.c_str(), "SQL Error", •MB_OK) ;
) // Деактивировать предыдущий запрос и очистить SQL
Queryl->Close ();
Queryl->SQL->Clear();
// Восстановить команду SELECT Queryl->SQL->Add(sqls) ;
try
{
Queryl->0pen (); // выполнить команду SELECT }
catch(EDBEngineError* dbError) // обработка ошибок BDE {
for (int i=0; i<dbError->ErrorCount; i++)
MessageBox(0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ;
} }
Листинг 5.8. Использование метода ExecSQL с командой UPDATE.
Рис. 5.16 показывает пример работы приложения, вызывающего повышение заданной параметром mins минимальной зарплаты на величину процентной надбавки, определяемой параметром adds. Приложение представляет пользователю список служащих, зарплата которых осталась меньше установленного минимума. Сравните последний столбец таблицы с оригинальными значениями (Рис. 5.15).
Рис. 5.16. Результат выполнения динамического запроса с модификацией данных.
Аналогично таблице, компонента запроса также инкапсулирует следующие методы:
• First, Next, Prior, Last и MoveBy используются для навигации по результатам динамического запроса.
• Append, Insert, AppendRecord и InsertRecord добавляют новую запись к таблице. Delete вычеркивает текущую запись. Edit разрешает приложению модифицировать записи, a Post вызывает фактическое изменение содержимого базы данных.
5.2.1.4 Хранимые процедуры
Хранимая процедура представляет собой инкапсуляцию команд и данных (таблиц, индексов, областей значений) в некотором мета-объекте базы данных удаленного сервера. Компонента TStoredProc позволяет выполнить часто повторяющуюся процедуру, хранимую на сервере, и передать результаты приложению клиента.
Операции над большими группами строк в таблице базы данных, агрегатные или математические функции — подходящие кандидаты для хранимых процедур. Перемещая на мощный сервер такие повторяющиеся задачи с интенсивными вычислениями, можно заметно улучшить производительность вашего приложения. Общая загруженность сети при этом снижается, поскольку обработка происходит там же, где находятся сами данные.
Для примера рассмотрим приложение, задачей которого является вычисление единственной величины - среднеквадратичного отклонения значений по большой выборке записей. Для реализации этой функции ваше приложение должно получить по сети от сервера все значения, участвующие в вычислении, а затем произвести подсчет СКО. Цель вашего приложения - конечный результат в виде единственного числа - можно было бы достичь гораздо более эффективно с помощью хранимой на сервере процедуры, которая считывает данные "на месте" и передает именно то конечное значение, которое требовалось вашим приложением.
Рис. 5.17 показывает свойства компоненты хранимой процедуры в окне Инспектора объектов:
Рис. 5.17. Свойства хранимой процедуры. Active разрешает или запрещает режим просмотра "живых данных", возвращаемых процедурой на этапе проектирования. Значение false устанавливается по умолчанию.
DatabaseName содержит псевдоним адресуемого сервера базы данных.
StoredProcName позволяет выбрать имя нужной процедуры из выпадающего списка имен процедур, хранимых на данном сервере.
ParamBindMode задает метод, по которому фактические параметры ставятся в соответствие формальным параметрам в описании хранимой процедуры. Значение pbByName (по умолчанию) определяет соответствие по именам, а значение pbByNumber - по порядку перечисления в процедуре.
Params используется для ввода параметров хранимой процедуры (если таковые имеются). Редактор параметров, который открывается двойным щелчком мышью в графе значений этого свойства, обращается к серверу для выборки информации о входных и выходных параметрах. Для некоторых серверов
полная информация, необходимая для запуска хранимой процедуры, может оказаться недоступной. В таком случае вам придется самому определить тип каждого параметра (Input, Output, Result), тип его данных и, возможно, значения входных параметров. Редактор параметров отображает параметры в том поряд-
ке, в котором они перечислены в описании данной хранимой процедуры (Рис. 5.18). Нажатие кнопки ОК подготавливает сервер и вызывает запуск хранимой процедуры на стадии проектирования приложения. Только база данных Oracle позволяет добавлять, вычеркивать или удалять все параметры в определении хранимой на сервере процедуры, производя таким образом ее перегрузку. Отвечающие за такие действия кнопки редактора параметров Add, Delete и Clear обычно запрещены, поскольку ваше приложение клиента не может модифицировать хранимые процедуры других серверов.
Рис. 5.18. Редактор параметров хранимой процедуры.
Аналогично запросу, свойство Params содержит указатель на массив объектного типа TParams. Поэтому изменить значение параметра во время выполнения программы можно по индексу в массиве Items объектов типа TParams:
StoredProcl->Params[0]->Items[1]->AsString = Editl->Text;
или по имени параметра, посредством метода ParamByName:
StoredProcl->ParamByName("ЧИСЛО_СЛУЖАЩИХ")->AsInteger++;
а затем подготовить сервер методом Prepare и выполнить процедуру методом ЕхесРгос:
StoredProcl->Prepare () ;
StoredProcl->ExecProc ();
Хранимая процедура возвращает результаты через выходные параметры или через результирующий набор. Доступ к выходным параметрам во время выполнения программы (как и модификация значений входных параметров перед запуском хранимой процедуры) осуществляется по индексам в массиве Items объектов типа TParams:
Editl->Text = StoredProcl->Params[0]->Items[0]->AsString;
или по имени параметра, посредством метода ParamByName:
Edit2->Text =
StoredProcl->ParamByName("ЧИСЛО_СЛУЖАЩИХ")->AsInteger;
Хранимые процедуры некоторых серверов (например, Sybase) возвращают, подобно запросу, результирующий набор, для отображения которого надо использовать подходящую компоненту управления - чаще всего сетку. Процедура проектирования формы приложения с компонентой TStoredProc аналогична той, которую мы использовали для отображения результатов запроса:
1. Установите псевдоним адресуемой базы данных сервера в свойстве DatabaseName.
2. Поместите на форму компоненту TDataSource и установите ее свойство DataSet = StoredProcl.'
3. Поместите на форму компоненту управления сеткой TDBGrid и установите ее свойство DataSource = DataSourcel.
4. Поместите компоненту TStoredProc на форму.
5. Укажите имя процедуры в свойстве StoredProcName.
6. Установите свойство Active = true для процедуры StoredProcl с тем, чтобы сразу же отобразить результаты в сетке.
7. Откройте редактор параметров, введите (если надо) их значения и нажмите кнопку ОК.
5.2.1.5 Соединения с базой данных и транзакции
Компонента TDatabase позволяет создавать в приложении локальный BDE псевдоним базы данных, таким образом не требуя его наличия в конфигурационном файле BDE. Этот локальный псевдоним может использоваться другими компонентами доступа. Кроме того, с помощью TDatabase можно разработать оригинальный процесс первого соединения с сервером (login), подавляя некоторые подсказки и автоматически подставляя значения необходимых параметров. Наконец, и что наиболее важно, TDatabase способна поддерживать одиночное соединение с базой данных, концентрируя в себе все необходимые операции для поддержания транзакции.
Классическим примером транзакции является перевод денежных средств банковских счетов. Такая транзакция обычно состоит в добавлении определенной суммы перевода к новому счету и вычитании этой суммы из исходящего счета. Если выполнение любой из этих операций терпит неудачу, весь трансферт считается незавершенным. SQL серверы дают возможность "прокручивать назад" команды при возникновении ошибки, не производя никаких изменений в базе данных. Именно управление транзакциями является функцией компоненты TDatabase. Как правило, транзакция содержит несколько команд, поэтому начало транзакции надо отметить методом StartTransaction. Как только транзакция началась, все ее исполняемые команды находятся во временном состоянии, до тех пор, пока один из методов Commit или Rollback () не отметят конец транзакции. Вызов Commit фактически модифицирует данные, а вызов Rollback отменяет всякие изменения.
Рис. 5.19 показывает свойства компоненты соединения с базой данных в окне Инсектора объектов:
Рис. 5.19. Свойства соединения с базой дачных. AliasName содержит псевдоним существующей базы данных, определенный утилитой конфигурации BDE. Указание этого свойства является альтернативой значения DriverName.
DatabaseName позволяет создать локальный псевдоним базы данных в дополнение к значениям AliasName или DriverName.
DriverName содержит имя драйвера BDE при создании локального псевдонима по значению DatabaseName. Указание этого свойства является альтернативой значения AliasName.
Params содержит строчный массив параметров одиночного соединения.Листинг 5.9 реализует транзакцию по изменению адреса фирмы на примере связанных таблиц CUSTOMER и ORDERS. Старый адрес, введенный пользователем в область редактирования EditOld, заменяется на новый, введенный в область редактирования EditNew. В этом примере компонентный объект Databasel использовался для одиночного соединения с базой данных, поддерживающего выполнение одиночной транзакции. Этот объект необходимо каким-то образом связать с псевдонимом базы данных - или установкой соответствующих свойств компоненты, или определив параметры соединения (такие как тип драйвера, имя сервера, имя пользователя, пароль) во время выполнения программы. Сначала опробуем первый способ соединения на стадии проектирования формы приложения, установив значения свойств компоненты, как показано на Рис. 5.19.
void_fastcall TFormI::ButtonlClick(TObject *Sender) {
char sqls[250]; // массив для хранения команды SQL try
{
Databasel->StartTransaction ();
Queryl->SQL->Clear () ;
// Изменить EditOld на EditNew в таблице CUSTOMER sprintf(sqls, "UPDATE CUSTOMER set Addrl = \"%s\" WHERE (Addrl = \"%s\")", EditNew->Text.c_str(), Edit01d->Text.c_str());
Queryl->SQL->Add(sqls) ;
Queryl->ExecSQL ();
Queryl->SQL->Clear () ;
// Изменить EditOld на EditNew в таблице ORDERS sprintf(sqls, "UPDATE ORDERS set ShipToAddrl = \"%s\" WHERE (ShipToAddrl = \"%s\")", EditNew->Text.c_str(), Edit01d->Text.c_str()) ;
Queryl->SQL->Add(sqls) ;
Queryl->ExecSQL();
// Внести все изменения, сделанные до этого момента Databasel->Commit();
Tablel->Refresh();
Table2->Refresh() ;
}
catch(EDBEngineError* dbError) // обработка ошибок BDE {
for (int i=0; i<dbError->ErrorCount; i++) MessageBox (0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ;
Databasel->Rollback() ;
return;
} catch (Exception* exception) // обработка исключений
{
MessageBox (0, exception->Message.c_str (), "Error", MB_OK) ;
Databasel->Rollback() ;
return;
) }
Листинг 5.9. Транзакция, реализующая смену адреса фирмы.
Конечно, показанный на Рис. 5.20 пример прохождения транзакции в соединении с локальным сервером не имеет особой практической ценности и приводится здесь только с целью проиллюстрировать работу компоненты TDatabase.
Рис. 5.20. Результат прохождения транзакции.
Листинг 5.10 показывает, как соединиться с сервером базы данных во время выполнения программы, не создавая ее псевдонима. Ключевые моменты заключаются в том, чтобы указать DriverName и заполнить массив параметров информацией, необходимой для первого соединения (в нашем примере пользователь вводит свое имя и пароль в объекты компонент редактирования Editi и Edit2). Мы определили только те параметры соединения, которые не установлены для данного драйвера в утилите конфигурации BDE. Значение параметра SQLPASSTHRU MODE определяет один из трех возможных способов взаимодействия табличных методов Add, Append и Insert с компонентами запросов, которые соединены с той же базой данных. Использованное в примере значение NOT SHARED означает, что табличные методы и запросы используют два раздельных соединения с сервером. Сервер рассматривает их как соединения с двумя разными пользователями. До тех пор, пока транзакция не завершится, табличные методы не применяются, хотя результаты выполнения запросов могут менять содержимое базы данных, раздельно от действий активной транзакции. Два других значения SHARED NOAUTOCOMMIT и SHARED AUTOCOMMIT указывают, что табличные методы и запросы разделяют одно общее соединение с сервером. Если вам нужно включить табличные методы в транзакцию, используйте способы SHARED NOAUTOCOMMIT или NOT SHARED.
void_fasfccall TFormI::ButtonlClick(TObject *Sender) (
char name[20]; // буфер для имени пользователя char pass [20]; // буфер для пароля try
{
// Закрыть базу данных и установить параметры Databasel->Close() ;
Databasel->DriverName = "STANDARD";
Databasel->KeepConnection = true;
Databasel->LoginPrompt = false;
Databasel->Params->Add("SERVER NAME=
...\\CBuilder\\EXAMPLES\\DATA\\EMPLOYEE.DB") ;
Databasel->Params->Add("SCHEMA CACHE=8") ;
Databasel->Params->Add("OPEN MODE=READ/WRITE") ;
Databasel->Params->Add("SQLPASSTHRU MODE=NOT SHARED");
sprintffname, "USER NAME=%s", Editl->Text.c_str ());
Databasel->Params->Add(name);
sprintf(pass, "PASSWORD=%s", Edit2->Text.c_str());
Databasel->Params->Add(pass);
// Снова открыть базу данных и указанную таблицу Databasel->0pen() ;
Tablel->0pen() ;
}
catch(EDBEngineError* dbError) // обработка ошибок BDE
{
for (int i=0; i<dbError->ErrorCount; i++) MessageBox(0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ;
} )
Листинг 5.10. Соединение с сервером без псевдонима.
Конечно, первое вхождение в локальную демонстрационную базу данных BCDEMOS не требует задания имени пользователя и пароля, однако, используя такую методику, вы сможете стандартизовать процесс соединения с обычно защищенными базами данных удаленных серверов. Рис. 5.21 иллюстрирует работу прототипа подобного приложения.
Рис. 5.21. Первое соединение с "защищенной" базой дачных.
5.2.2 Компоненты управления данными
Компоненты управления служат для отображения и редактирования наборов данных на форме в удобном для пользователя виде. Свойство DataSource замыкает трехступенчатую связь любой из компонент управления с компонентагли доступа к содержимому базы данных. Вы должны связать выбранную компоненту управления с набором данных посредством компоненты источника TDataSource, который определяется значением свойства DataSource.
Устройство и работа большинства из этих интерфейсных элементов довольно очевидны, их эквиваленты знакомы нам по вкладке Standard стандартных Windows Палитры компонент, а глава 4 содержит подробное их описание.
Остановимся на особенностях использования исключительно важной и мощной компоненты навигатора базы данных TDBNavigator. Эта компонента придает приложениям СУБД новый стандартизованный облик с панелью управления как у видеомагнитофона (Рис. 5.22).
Рчс. 5.22. Панель навигатора.
Нажимая на кнопки First, Prior, Next и Last, пользователь перемещается от записи к записи, а с помощью кнопок Insert, Delete, Edit, Post, Cancel и Refresh про-
изводит редактирование записей. Именно навигатор обеспечивает ту степень функциональности, в которой нуждается любое современное приложение баз данных. Все кнопки навигатора видимы по умолчанию.
Рис. 5.23 показывает свойства компоненты навигатора базы данных в окне Инспектора объектов:
Рис. 5.23. Свойства навигатора базы данных. DataSource соединяет кнопки управления панели навигатора с компонентами доступа к наборам данных через компоненту источника. Изменяя значение этого свойства во время выполнения программы, можно использовать одну и ту же компоненту для навигации по разным таблицам. Разместите на форме две компоненты редактируемого ввода DBEditI и DBEdit2, связанные с таблицами CustomersTable и OrdersTable через источники данных CustomersSource и OrdersSource, соответственно. Когда пользователь выбирает название фирмы (поле Company в DBEditI), навигатор тоже должен соединяться с источником CustomersSource, а когда активизируется номер заказа (поле OrderNo в DBEdit2), навигатор должен переключаться на источник OrdersSource. Чтобы реализовать подобную схему работы навигатора, необходимо написать обработчик события OnEnter для одного из объектов компоненты редактирования, а затем разделить это событие с другим объектом (Листинг 5.11).
VisibleButtons позволяет убрать ненужные кнопки, например, все кнопки редактирования на форме, предназначенной исключительно для просмотра данных. В приложениях клиент/сервер рекомендуется запретитькнопки First и Last, так как их нажатие может проявляться в длительном прохождении запросов.
Во время выполнения программы можно динамически прятать или вновь показывать кнопки навигатора - в ответ на определенные действия пользователя или на изменения состояния приложения. Предположим, вы предусмотрели единый навигатор для редактирования таблицы CustomersTable и для просмотра таблицы OrdersTable. Когда навигатор подключается ко второй таблице, желательно спрятать кнопки редактирования Insert, Delete, Edit, Post, Cancel и Refresh, а при подключении к первой таблице - снова показать их. Листинг 5.11 показывает законченный текст обработчика события OnEnter с добавлениями кода для манипуляций кнопками панели навигатора.
void_fastcall TFormI:: DBEditlEnter(TObject *Sender) {
if (Sender == DBEditI)
{
DBNavigator->DataSource = CustomersSource;
Set<TNavigateBtn, 0, 9> btnShow;
btnShow “ nbFirst“nbPrior“ nbNext“nbLast“nbInsert“ nbDelete“nbEdit“nbPost“nbCancel“nbRefresh;
DBNavigator->VisibleButtons = btnShow;
}
else
{
DBNavigator->DataSource = OrdersSource;
Set<TNavigateBtn, 0, 9> btnShow;
btnShow “ nbFirst“nbPrior“nbNext“nbLast;
DBNavigator->VisibleButtons = btnShow;
} }
Листинг 5.11. Переключение значения свойства DataSource навигатора и манипуляции со свойством VisibleButtons.
ShowHint разрешает или запрещает высвечивать подсказку с названием кнопки навигатора, когда на нее наведен курсор. Значение false (устанавливается по умолчанию) запрещает подсказки для всех кнопок.
Hints содержит массив текстовых подсказок для каждой кнопки навигатора. Используя редактор списка, который открывается двойным щелчком мышью в графе значений этого свойства, можно дать оригинальные названия кнопкам.
Рис. 5.24 показывает работу приложения, использующего все описанные возможности навигатора.
Рис. 5.24. Разделение навигатора между двумя таблицами.
5.3 Итоги
C++Builder - это система программирования общего назначения, которая может использоваться для быстрой разработки любых приложений, в том числе одних из самых сложных — Систем Управления Базами Данных.
Визуальное построение простого интерфейса пользователя с таблицей некоторой базы данных оказывается делом нескольких часов, а иногда и нескольких минут. Не намного больше времени потребуется для организации SQL запросов к локальному или удаленному серверу базы данных, даже с переменным числом параметров или кэшируемыми обновлениями.
Итак, проектирование формы приложения СУБД в среде C++Builder в простейшем случае требует выполнения всего лишь трех или четырех действий:
1. Перенесите на форму компоненту набора данных (TTable или TQuery) из вкладки Палитры Data Access и установите ее свойства в соответствии со специфическими требованиями выбранной базы данных.
2. Перенесите на форму компоненту источника данных[ТОа1а5оигсе и в свойстве DataSet укажите ссылку на объект набора данны^ (например. Table 1 или Query 1).
3. Перенесите на форму нужные компоненты отображения и редактирования данных из вкладки Data Controls и в их свойстве DataSource задайте источник данных (например, DataSourcel). Определите отображаемое поле набора данных в свойстве DataField. '
4. Если на предыдущем шаге вы выбрали компоненту сетки TDBGrid, то используйте ее совместно с компонентой навигатора TDBNavigator.