Интернет системы
Работа с компонентами IBX
или использование InterBase eXpress в приложениях на Delphi и C++Builder, с СУБД Firebird и InterBase
kdv, www.ibase.ru, 23.02.2005
обновления: 20.05.2005, 23.05.2005, 24.05.2005, 26.05.2005 - 1, 2, 3, 16.10.2005, 06.04.2006, 30.08.2006, 15.02.2007, 09.03.2007, 28.03.2008, 01.09.2008, 10.06.2010, 10.05.2011, 27.01.2012.
Введение
В Delphi 3 в иерархию компонент
работы с данными была заложена возможность
написания собственных
Но в Delphi 3 ситуация изменилось, и в поставку был включен пример работы с текстовым файлом - (он включен в поставку Delphi до сих пор) Demos/DB/TextData. В результате стали появляться наборы компонент, которые позволяли работать напрямую с любыми источниками данных, в том числе с SQL-серверами. Первым набором таких компонент для InterBase был FreeIBComponents.
FreeIBComponents состоял всего
из четырех компонент - TFIBDatabase,
TFIBTransaction, TFIBDataSet и TFIBQuery. Дополнительный,
пятый, компонент
Перед выходом InterBase 6, примерно в конце 1999 года, FreeIBComponents прекратил свое существование, поскольку был передан в Borland для разработки компонент IBX (InterBase eXpress), которые должны были быть включены в поставку Delphi и C++Builder. Одновременно исходные тексты FreeIBComponents были взяты за основу для создания библиотеки FIBPlus Сергеем Бузаджи.
Исходный набор компонент был существенно расширен как в IBX так и в FIBPlus, и в этой статье будут рассмотрены компоненты IBX, поставляющиеся в Delphi 7.
С помощью IBX (и FIBPlus) можно работать с любыми версиями InterBase, Firebird и Yaffil. Это означает, что все версии IBX и FIBPlus работают (и будут продолжать работать) со всеми версиями InterBase, Firebird и Yaffil, во всех средах разработки Delphi (3,4,5,6,7,2005, 2006, 2007, 2009, 2010), и C++Builder (версий 1, 2, 3, 4, 5, 6, 2006, 2007, 2009, 2010).
!!! В Delphi 2009, 2010, XE и XE2 поставляется
новая версия IBX, которая имеет
несовместимости с базами
Если Вы используете "чистую" установку Firebird, в которой клиентская gds32.dll называется иначе (fbclient.dll) - воспользуйтесь утилитой instclient.exe из комплекта Firebird.
Обновления
Для IBX нужно обязательно устанавливать обновления, которые можно скачать либо с оригинальной страницы, либо здесь.
Компоненты
Закладка InterBase на палитре компонент в Delphi и C++Builder. Есть в любой версии Delphi/C++Builder (кроме бесплатного Turbo Explorer)
Практически каждый компонент имеет свой собственный редактор свойств, который вызывается по двойному клику на него (если он находится на форме или в DataModule), или в отдельном пункте меню, вызываемому по правой кнопке мыши.
Клиентская библиотека
IBX может работать только с библиотекой gds32.dll. Для работы с Firebird необходимо при помощи входящей в комплект утилиты instclient создать gds32.dll из fbclient.dll, т.к. компоненты ориентируются на версию gds32.dll не ниже 6.0, а в fbclient.dll указана версия Firebird, которая ниже 6.0 (1.5, 2.0, 2.1, 2.5).
Usage:
instclient i[nstall] [ -f[orce] ] library
q[uery] library
r[emove] library
Наиболее удобным является размещение gds32.dll рядом с вашим exe. В этом случае приложение загрузит именно эту библиотеку, а не какую то другую, например из System32.
Также нужно помнить, что компоненты в IDE тоже загружают gds32.dll. Поэтому перед началом работы необходимо проверить диски на присутствие лишних, старых или "неправильных" gds32.dll.
Также см. Установка только клиентской части.
Организация доступа к данным
Исключительно для начинающих:
Взаимосвязи компонент выглядят следующим образом:
первым является IBDatabase. Это
центральный компонент для
вторым является IBTransaction. Вне контекста транзакции в InterBase и Firebird нельзя выполнить никаких действий с данными и метаданными БД.
IBTransaction1.
третьим является либо датасет (IBDataSet, IBQuery), либо IBSQL. он связывается с базой данных и транзакцией
IBQuery1.Database:=
IBQuery1.Transaction:=
четвертый - источник данных для датасета, т.е. универсальный TDataSource.
DataSource1.DataSet:=IBQuery1;
пятым является DBGrid. Он связывается только с DataSource.
DBGrid.DataSource:=
Легче всего данные связи осуществить прямо в дизайнере, в свойствах компонент, помещая их по очереди (в указанном порядке) на форму. То же самое (установку взаимосвязей) можно выполнить в коде, в аналогичной последовательности.
Для вывода данных в грид требуется заполнить IBQuery1.SQL запросом на выборку (например, select * from employee), а затем
IBDatabase1.Open;
IBTransaction1.
IBQuery1.Open;
О том, какие свойства IBDatabase, IBTransaction и т.п. необходимо менять и настраивать, читайте дальше.
Необходимо отметить, что в InterBase и Firebird является нормальным
привязка нескольких IBTransaction к одному IBDatabase. При этом операции в разных транзакциях можно совершать по очереди без проблем.
привязка нескольких датасетов (IBDataSet, IBQuery, IBSQL и т.п.) к одной транзакции. С датасетами в одной транзакции также можно работать по очереди без проблем.
TIBDatabase
Предназначен для осуществления соединения с базой данных.
Основные свойства:
DatabaseName - имя сервера и путь к базе данных (или алиас, если поддерживается сервером). Например - localhost:c:\dir\data.gdb, server:c:\dir\data.gdb.
при ошибках локального соединения (путь к БД без имени сервера) см. пункт FAQ.
Params - параметры соединения: имя пользователя, пароль, чарсет и т.п.
Если воспользоваться редактором свойств TIBDatabase (двойной клик на компоненте), то упомянутые свойства будут заполнены автоматически. Например, для базы данных, созданной с default character set win1251 параметры будут такими:
user_name=SYSDBA
password=masterkey
lc_ctype=WIN1251
На диалоге редактора свойств есть кнопка Test, которая позволяет проверить соединение с указанными сервером и базой данных. Если проверка прошла нормально, то можно установить свойство Connected в True.
Для задания свойств остальных компонент (TIBTable, TIBQuery, TIBDatabase) потребуется соединение с сервером БД при помощи вышеупомянутого Connected:=True.
В параметрах TIBDatabase можно
дополнительно прописывать
Дополнительные свойства:
Connected - управление подсоединением к БД, или проверка состояния соединения
DefaultTransaction - компонент IBTransaction,
который будет использоваться
в качестве умолчательного для
выполнения различных операций IBDatabase.
Если это свойство не
IdleTimer - по умолчанию 0. Определяет время, в течение которого при отсутствии активных действий соединение с БД закроется автоматически.
SQLDialect - 1 или 3. По умолчанию
3. Определяет диалект, в котором
будет работать клиентская
TraceFlags - перечень действий между клиентом и сервером, которые будет отслеживать компонент IBSQLMonitor или внешние приложения, поддерживающие такую функциональность.
Подсоединение к БД
Соединиться с базой можно установив один раз параметры при помощи вышеописанного диалогового окна. Если требуется заполнить параметры соединения вручную, в том числе вызывая свой собственный диалог запроса имени пользователя и пароля, то можно использовать следующий код:
IBDatabase1.Params.Clear;
IBDatabase1.LoginPrompt:=
IBDatabase1.DatabaseName:='
IBDatabase1.Params.Add('user_
IBDatabase1.Params.Add('
IBDatabase1.Params.Add('lc_
IBDatabase1.Connected:=True;
Замена диалога запроса имени пользователя и пароля
Выше уже было сказано, что можно использовать свой диалог для запроса имени пользователя и пароля. Его можно "вставить" в качестве замены стандартного, при LoginPrompt:=True. Пример:
procedure TForm1.IBDatabase1Login(
var
dlg: TDBLoginDialog; // созданный вами диалог
begin
dlg:=TDBLoginDialog.Create(
if dlg.ShowModal = mrOK then
with LoginParams do
begin
Values['USER_NAME'] := User_Name;
Values['PASSWORD'] := User_Pass;
// другие параметры, например role_name, lc_ctype и т.д., если необходимо
end;
dlg.Free;
end;
Динамическое создание диалога не обязательно, но желательно, чтобы он зря не занимал в приложении память, тем более что диалог этот обычно вызывается 1 раз при старте приложения.
Создание БД
Обычно базу данных создают в IBConsole, IBExpert, IBDevStudio, isql и вообще любом инструменте разработчика. Из приложения БД создавать обычно не принято, т.к. в этом случае потребуется все метаданные (структуры таблиц, триггеров и т.п.) создавать опять же в приложении, что увеличит размер приложения. Гораздо проще вместе с приложением поставлять пустую или наполненную частично базу данных. Однако, создать БД из приложения не проблема. Вот как это можно сделать
IBDatabase1.Params.Clear;
IBDatabase1.DatabaseName:='
IBDatabase1.Params.Add('user ''SYSDBA'' password ''masterkey'' ');
IBDatabase1.Params.Add('page_
IBDatabase1.Params.Add('
IBDatabase1.CreateDatabase;
IBDatabase1.Connected:=False;
После чего можно подсоединиться к БД любым способом, указанным выше. Последняя строка в этом примере (Connected:=False) нужна потому, что в IBX после CreateDatabase база остается в открытом состоянии, но не задан чарсет подсоединения к БД, а также неполноценно инициализировано соединение с БД в IBX. Именно поэтому после создания БД к ней нужно снова подсоединиться нормальным способом.
! При создании БД разные версии серверов InterBase и Firebird могут по разному устанавливать параметр БД Forced Writes. Если Forced Writes = Off, то в случае сбоя компьютера содержимое базы данных может быть повреждено. В статье изложено, как установить правильно ForcedWrites, как избежать повреждений БД и как ее отремонтировать в случае сбоя.
При использовании IBDatabase (IBX)
в многопоточных (multithreaded) приложениях,
в том числе с web- или com-серверами,
нужно соблюдать следующие
соединение с БД не должно быть "локальным". То есть не c:\dir\data.gdb, а сетевым - localhost:c:\dir\data.gdb.
Если при использовании локального соединения возникает ошибка - читайте FAQ.
в одном thread допускается работа только с одним IBDatabase.
Попытка осуществить работу с одним коннектом из разных threads может быть успешной, если использовать блокировки thread при обращении к этому коннекту (на мютексах, семафорах и т.п.). Но в результате работа всего приложения будет не многопоточной, то есть, превратится в псевдо-многопоточное по причине блокировок между threads при работе с одиним коннектом. Действительно параллельно в разных thread могут выполняться только операции над разными коннектами (IBDatatabase).
иногда при подсоединении к БД в созданном thread может возникнуть ошибка на вызове isc_attach_database (собственно на функции, которая и осуществляет соединение к БД при вызове IBDatatase.Connected:=True). В этом случае вынесите открытие соединения в главный thread приложения, а дальнейшие операции с коннектом производите в пределах нужного thread.
TIBTransaction
Компонент для явной работы с транзакциями.
Клиентская часть InterBase допускает
выполнение любых действий только в
контексте транзакции. Поэтому если
вы смогли получить доступ к данным
без явного вызова IBTransaction.
Основные свойства:
Active - управление стартом
или завершением транзакции, а
также проверка состояния
! при вызове Active:=False транзакция будет безусловно завершена по Rollback !
DefaultDatabase - компонент TIBDatabase
Params - параметры транзакции
AllowAutoStart - если False, то любые
попытки автоматического
IdleTimer - если не 0, то время,
через которое транзакция
DefaultAction - результат автоматического
завершения транзакции в
Умолчательное значение зависит от версии компонент, и может быть как taRollback так и taCommit.
AutoStopAction - свойство, аналогичное
DefaultAction по значениям (плюс saNone, по
умолчанию), указывает на метод
завершения транзакции, когда все
DataSet-ы, подключенные к ней,
В параметрах транзакции (картинка
слева, вызывается по двойному клику
на компоненте, или при нажатии
кнопки ... в свойстве Params) указываются
константы, соответствующие IB API (без
префикса isc_tpb_). Если параметры не указаны
(пусто), то IBX использует параметры
транзакции по умолчанию, которые соответствуют
уровню изолированности snapshot+wait+write (FIBPlus,
в отличие от IBX, при пустых параметрах
самостоятельно прописывает параметры"read
write read_committed no wait", т.е. с другим уровнем
изолированности и режимом
В приложении можно использовать сколько угодно компонентов TIBTransaction. Худшими случаями являются как один компонент на все приложение (пример ibmastapp из Delphi), так и по компоненту на каждый выполняемый запрос. Вы должны определить сами, сколько вам нужно экземпляров TIBTransaction, в соответствии с задачами вашего приложения.
! В коде не рекомендуется
для старта или завершения
транзакции использовать property Active -
явный вызов методов
Для работы в режиме "только чтение" (например со справочниками или при использовании с "двутранзакционными" датасетами, т.е. раздельными транзакциями для чтения и записи) в FB 1.x и IB 7.x рекомендуется использовать параметры
read
read_committed
rec_version
такая транзакция в InterBase 6.0 и выше (все версии IB 7.0, 7.1, 7.5, Firebird и Yaffil) может быть открытой сколь угодно долгое время (дни, недели, месяцы), без блокирования других транзакций или влияния на накопление мусора в базе данных (потому что на самом деле на сервере такая транзакция стартует как committed). Однако, такая транзакция (read_committed) во время перечитывания данных будет видеть все новые committed-изменения, и например, для отчетов, ее использовать нельзя. Более подробно о параметрах транзакций см. документ.
Подробнее об управлении транзакциями см. раздел "Использование и управление IBTransaction в приложениях"
Датасеты
Работать с данными в IBX можно при помощи компонент IBDataSet, IBQuery, IBTable, IBStoredProc, IBSQL, но IBSQL не является датасетом, а IBStoredProc хоть и является датасетом, получать из него выборки невозможно (см. далее).
TIBDataSet, TIBQuery, IBTable, TIBStoredProc унаследованы от TIBCustomDataSet.
IBDataSet
TIBDataSet = class(TIBCustomDataSet)
Основной компонент, пришедший из FreeIBComponents. Остальные, то есть IBTable, IBQuery (+IBUpdateSQL) - всего лишь модификации, либо чуть расширенные, либо усеченные. Возможностей этого компонента хватает практически для любых целей.
Назначение компонента - буферизация
записей, выбираемых оператором SELECT, для
представления этих данных в Grid, а
также для обеспечения "редактируемости"
записи (текущей в буфере (гриде))
путем автоматического или
! для выполнения отдельных запросов insert, update, delete, execute procedure и операторов DDL вместо IBDataSet следует использовать IBQuery или IBSQL.
Основные свойства:
BufferChunks - размер буфера (число записей), аллокируемого IBDataSet-ом за 1 раз.
Database - связь с компонентом IBDatabase
DataSource - ссылка на Master-источник данных (TDataSource) для IBDataSet, используемого в качестве Detail.
ForcedRefresh - при False перечитывание
данных (текущей записи) производится
только при явном вызове Refresh.
При True - происходит автоматически
при Post. Вызов метода Refresh будет
перечитывать только одну, текущую
запись. Для перечитывания всего
набора нужно закрыть и
GeneratorField - по нажатию на кнопке ... выводится диалог для конфигурирования "автоинкремента" - какому столбцу, какой генератор, с каким шагом, и по какому действию (onNewRecord, onPost, onServer). Это автоматический вызов инкремента значения генератора и помещения его в столбец, вместо ручного использования генераторов.
ParamCheck - проверять или нет наличие в запросе параметров (:param). По умолчанию True. Для выполнения операторов DDL (create procedure, alter, drop и т.п.) нужно выставить в False.
Transaction - к какой транзакции привязан компонент.
UniDirectonal - при False все выбираемые записи кэшируются в буфере IBDataSet, что позволяет после выборки всех записей перемещаться от начала до конца выборки и обратно не обращаясь к серверу. При True размер буфера ограничен, просмотр записей "вверх" на определенном этапе будет невозможен.
UpdateObject - свойство для подключения IBUpdateSQL, не нужно.
Запросы:
SelectSQL - основной запрос, который возвращает данные.
RefreshSQL - запрос для обновления текущей строки. Должен содержать условие отбора по первичному ключу или подобное, для выборки одной записи.
InsertSQL - запрос для вставки записи
UpdateSQL - запрос для обновления записи
DeleteSQL - запрос для удаления записи
Для активации IBDataSet необходимо выполнить ряд действий:
Соединить его с нужным
IBDatabase, и с IBTransaction (если это не тот IBTransaction,
который указан для IBDatabase.DefaultTransaction)
Определить запрос, выбирающий данные. В этот момент подсоединенные IBDatabase и IBTransaction должны быть активны. Запрос можно указать
щелкнув на кнопку ... свойства SelectSQL
нажав на компоненте правую кнопку, выбрать в меню Edit SQL
Если редактирования данных, возвращаемых запросом SelectSQL, не предполагается, то на этом можно остановиться - после Active:=True компонент выберет данные (которые будут видны, если к IBDataSet уже подсоединены TDataSource и TDBGrid). Иначе можно задать запросы Insert/Update/Delete/Refresh. Это можно сделать вручную (прописав запрос в окне, выводимом по кнопке ... у конкретного свойства), или автоматически. Нажмите на IBDataSet правую кнопку, выберите DataSet Editor. Появится диалоговое окно. Базовая информация (имя таблицы, столбцы), уже получена из Select SQL. В диалоге нужно указать столбцы первичного ключа (key fields) и обновляемые столбцы (update fields). Как правило, столбцы первичного ключа не обновляются, поэтому их исключают из Update Fields. Теперь можно нажать Generate SQL. Появится диалоговое окно с переключателями 4-х запросов, сгенерированными автоматически.
RefreshSQL - как было сказано выше, для считывания (перечитывания) одной записи (текущей в гриде, например).
InsertSQL - для вставки записи. Если столбцы первичного ключа не указаны в Update Fields, то они не попадут и в сгенерированный оператор SQL. То есть, нужно отредактировать этот запрос, добавив столбцы первичного ключа в оператор Insert (если только столбцы первичного ключа не заполняются на сервере, что делается редко, и также делает невозможным перечитывание этих данных после вставки).
DeleteSQL - для удаления текущей записи. Условия отбора в where должны совпадать с RefreshSQL.
UpdateSQL - для обновления текущей записи. Если столбцы первичного ключа не исключены из Update Fields, то они будут упомянуты в update set field =:field, и соответственно могут обновляться. Изменение значений столбцов первичных ключей - нехорошая операция, поэтому даже если на самом деле эти столбцы не меняются, их упоминание надо убрать из set данного оператора (но разумеется, оставить в where).
У IBDataSet есть специальные параметры, которые могут быть использованы в запросах. Параметры именуются автоматически, и соответствуют именам столбцов, предваряемые префиксами old_ и new_. Например - old_client_name, new_client_name. В old_-столбцах хранятся данные текущего столбца до модификации записи, а в new_ - те, которые ввел пользователь при редактировании.
простой пример:
допустим, есть таблица
create table X(
id int not null,
name varchar(30),
constraint PK_X primary key (id))
тогда:
SelectSQL = select * from X
InsertSQL = insert into X (id, name) values (:ID, :NAME)
ModifySQL = update X set NAME = :NAME where ID = :OLD_ID
DeleteSQL = delete from X where ID = :OLD_ID
RefreshSQL = Select ID, NAME from X where ID = :ID
Обратите внимание, что автоматически генерируемый UpdateSQL проверяет только соответствие первичного ключа (where field = :old_field). То есть, обновление записи произойдет независимо от старых или новых значений столбцов. Это поведение отличается от умолчательного поведения компонент BDE TTable и TQuery. У них было свойство UpdateMode, которое отвечало за проверки модификации записи. Это свойство позволяло контролировать возможное "перекрытие" обновлений для случаев, когда пользователь выполняет редактирование записи "долго", а другой пользователь может успеть отредактировать эту же запись и сохранить ее раньше. То есть, первый пользователь на этапе редактирования даже не будет знать, что запись уже изменилась, возможно не один раз, и сумеет "затереть" эти обновления своим:
upWhereAll (по умолчанию) - проверка на существование записи по первичному ключу + проверка всех столбцов на старые значения. например
update table set ... where pkfield = :old_ pkfield and client_name = :old_client_name and info = :old_info ...
То есть, в данном случае
запрос поменяет информацию в записи
только в том случае, если запись
до нас никто не успел изменить.
Особенно это важно, если существуют
взаимозависимости между
upWhereChanged - проверка записи
на существование по
update table set ... where pkfield = :old_pkfield and client_name = :old_client
В этом случае мы меняем имя клиента со старого на новое. Менялись ли остальные столбцы, пока мы редактировали запись, нас не интересует.
upWhereKeyOnly - проверка записи
на существование по
Последняя проверка соответствует
генерируемому автоматически
Как вы понимаете, это означает
необходимость динамического
Вместо запросов в InsertSQL/UpdateSQL/DeleteSQL (и вообще их все необязательно заполнять, можно только часть, например, если удаление не допускается) можно указывать вызов хранимых процедур. Это позволяет "редактировать" запросы, которые представляют собой объединения нескольких таблиц, агрегаты (группировки) и т.п. случаи, когда нет прямого соответствия между записями в таблицах и записями, возвращаемыми запросом.
! в коде приложения
для открытия и закрытия IBDataSet
рекомендуется вызывать его
Описание методов ExecQuery, Prepare аналогично IBQuery, см. дальше.
Буферизация записей
Открытие запроса, возвращающего набор записей (Active:=True или вызов Open), приводит всего лишь к выполнению запроса, но не к выборке записей. Выборка записей начинается только тогда, когда происходит вызов методов Next, Last или FetchAll (Locate рассмотрен дальше, отдельно). Соответственно, свойство RecordCount показывает только число выбранных в буфер (с сервера) записей. IBCustomDataset и его наследники после Open выбирают только 1 запись в буфер, на чем выборка записей останавливается. Если DataSet подключен к DBGrid, то DBGrid, показывая записи, автоматически вызовет DataSet.Next столько раз, сколько поместилось записей на экран. При дальнейшем продвижении в DBGrid будут считываться остальные записи. По достижении конца выборки (последней записи) сервер сообщает клиентской части что записи кончились, после чего все выбранные записи находятся в буфере DataSet, и любое продвижение по записям вверх или вниз (Next, Prev) приводит только к перемещению указателя внутри буфера DataSet (без обращения к серверу).
Если запрос не вернул ни одной записи, то оба свойства - BOF и EOF - будут равны True.
Обновление данных
У тех, кто уже знаком с механизмами работы технологии клиент-сервер, вопрос по обновлению данных, измененных другими приложениями, возникает только при неверно выбранном уровне изолированности (параметрах) транзакций (см. описание IBTransaction). У тех, кто только пришел с десктопных форматов БД (или вообще), и начинает работать с клиент-серверной СУБД впервые, возникает вопрос по обновлению данных другого рода - как это сделать вообще.
Если не вдаваться в подробности (также см. статью), то можно дополнить к предыдущему разделу (буферизация записей), что увидеть новые (committed) данные можно только выполнив запрос снова. Для этого, разумеется, нужно закрыть и открыть IBDataSet (или IBQuery/IBSQL). Переоткрытие (Close/Open) датасета приведет к уничтожению старого буфера записей, перевыполнению запроса, и считыванию записей в буфер. После чего опять до переоткрытия запроса изменения, выполненные другими приложениями (или в других транзакциях) опять будут "не видны".
Перебор записей
Для обработки всех записей, возвращаемых запросом, можно использовать цикл:
IBDataSet.Open;
while not IBDataSet.EOF do
begin
.. // действия с текущей записью
IBDataSet.Next;
end
Аналогичный код будет работать и для IBTable, IBQuery.
Master-Detail
Странно, но такой вопрос
часто возникает у
кладем IBDatabase, IBTransaction, соединяем их
кладем 2 IBDataSet (IBQuery) и 2 TDataSource. Соединяем IBDataSet с IBDataBase, IBTransaction и DataSource
В базе есть 2 таблицы - клиенты и заказы. Клиенты - master, заказы - detail.
IBDataSet1 + DataSource1 называем MDS и MasterSrc соответственно. Прописываем у MDS в SelectSQL запрос
select * from clients
IBDataSet2 + DataSource2 называем DDS и DetailSrc соответственно. Прописываем у DDS в SelectSQL запрос
select * from orders where client_id = :cid.
Здесь имя параметра :cid должно совпадать с именем столбца первичного ключа в таблице clients. В данном примере этот столбец и называется CID.
у DDS указываем свойство DataSource = MasterSrc.
Активируем IBDatabase, IBTransaction, затем сначала MDS, затем DDS (можно еще проверить на форме, нажав правую кнопку и в меню CreationOrder, что компоненты создаются и активируются (открываются) в правильном порядке, и MDS+MasterSrc создаются раньше чем DDS+DetailSrc).
Подсоединяем 2 DBGrid, к MasterSrc и DetailSrc
Запускаем приложение. При перемещение по гриду master показываются соответствующие записи таблицы detail
В дополнение к master-detail хочется
отметить еще один вопрос, который
как таковой не относится к IBX.
Иногда разработчики, не прочитав внимательно
статью по генераторам, пытаются генерировать
идентификатор мастера в

- Интернет-сленг
- Интернет СМИ
- Интернет-СМИ
- Интернет-СМИ
- Интернет-СМИ (возникновение, понятие, функции)
- Интернет СМИ среди других кaнaлов мaссовой коммуникaции
- Интернет СМИ среди других каналов массовой коммуникации
- Интернет – ресурсы как инструмент продвижения туристской дестинации
- Интернет-ресурсы на уроках иностранного языка
- Интернет рынок
- Интернет-рынок информационных ресурсов
- Интернет сайты в современном архивном деле
- Интернет-сервис
- Интернет системе СМИ