Предварительная подготовка
Прежде чем приступить к созданию серверных объектов, предназначенных для работы под управлением MTS, следует убедиться в том, что сам MTS и Delphi 4 установлены корректно. Во-первых, NT Option Pack, содержащий MTS, следует обязательно установить до установки Delphi. Тогда в процессе установки Delphi при обнаружении инсталляционной программой установленной копии MTS в него будет добавлен специальный "пакет" (package) BDE-MTS, содержащий объект BdeMTSDispenser (его можно обнаружить с помощью MTS Explorer, рис. 2).
Рис. 2. BdeMTSDispenser, зарегистрированный в Microsoft Transaction Server
Следует также убедиться, что данный объект поддерживает транзакции. C этой целью нужно из контекстного меню объекта BDEDispenser выбрать опцию Properties и в появившейся диалоговой панели выбрать закладку Transaction (рис. 3):
Рис. 3. Установка поддержки транзакций объектом MTS.
Далее следует запустить BDE Administrator, открыть страницу Configuration, выбрать раздел System/Init и установить значение параметра MTS POOLING равным TRUE. Только при этом значении данного параметра возможна поддержка транзакций и коллективное использование соединений с базами данных, доступных с помощью BDE (рис. 4).
Рис. 4. Установка опции MTS POOLING для коллективного использования соединений с базами данных
Для выполнения описанных ниже примеров следует создать три таблицы в трех разных базах данных. Первая из них требует наличия сервера IB Database (он входит в комплект поставки Delphi 4) и должна быть создана в базе данных IBLOCAL с помощью следующего скрипта:
CREATE TABLE STOCKTABLE ( GOODSNAME CHAR(30), PRICE FLOAT, GOODSNUMBER INTEGER NOT NULL)
В этой таблице будут содержаться сведения о товарах на складе (название, цена, порядковый номер, являющийся также первичным ключом этой таблицы).
Для генерации первичных ключей в этой таблице создадим также генератор:
CREATE GENERATOR GEN1; SET GENERATOR GEN1 TO 5
Можно ввести в таблицу какие-либо данные (рис. 5):
Рис. 5. Таблица STOCKTABLE, созданная на сервере IB Database
Следующую таблицу создадим в формате Paradox (например, с помощью Database Desktop) и поместим в базу данных DBDEMOS, поставляемую вместе с Delphi. Структура этой таблицы приведена на рис. 6.
Рис. 6. Структура таблицы delivery.db
В этой таблице будут храниться данные о заказах на доставку товаров со склада (номер позиции на складе, название товара, адрес доставки).
И, наконец, третья таблица формата dBase должна быть создана в произвольном каталоге, и этот каталог должен быть описан как псевдоним PAYDB (рис. 7):
Рис. 7. структура таблицы ord.dbf
В этой таблице будут содержаться сведения об оплате за заказы (номер позиции на складе, стоимость товара, адрес для высылки счета).
Распределенные транзакции при использовании этих таблиц будут описывать выбор товара на складе и оформление заказа на доставку. При этом в таблице заказов выбранная запись удаляется, и при этом в двух других таблицах появляются по одной записи с тем же значением первичного ключа.
Для создания серверного объекта следует со страницы Multitier репозитария объектов выбрать пиктограмму MTS Data Module (рис. 8).
Рис. 8. Выбор MTS Data Module из репозитария объектов.
Далее в появившейся диалоговой панели MTS Data Module Wizard следует ввести имя класса и выбрать способ работы с транзакциями (рис. 9).
Рис. 9. MTS Data Module Wizard
После этого будет сгенерирована стандартная библиотека типов, связанная с созданным модулем данных.
В созданный модуль данных поместим один компонент TSession, один компонент TDatabase, один компонент TProvider, один компонент TTable, два компонента TQuery (рис. 10):
Рис. 10. Модуль данных серверного объекта StockDM1, управляющего таблицей STOCKTABLE
Свойство AutoSessionName компонента TSession установим равным True. Свойство SessionName компонента TDatabase установим равным имени компонента TSession (это делается для того, чтобы не было конфликтов между именами различных пользовательских сессий внутри процесса MTS при создании нескольких однотипных объектов). Свяжем компонент TDatabase с псевдонимом IBLOCAL, установив его свойство LoginPrompt равным False (вполне очевидно, что в серверном объекте диалог ввода пароля появляться не должен - ведь клиентское приложение, использующее его, может находиться на удаленном компьютере, рис. 11).
Рис. 11. Параметры компонента TDatabase серверного объекта StockDM1
Свяжем компоненты TTable и TQuery с компонентом TDatabase, и в качестве значения свойства TableName выберем имя вновь созданной таблицы STOCKTABLE. Свяжем компонент TProvider с компонентом TTable.
Далее установим значения свойств SQL компонентов TQuery:
insert into STOCKTABLE values(:a,:b,GEN_ID(GEN1,1))
и
delete from STOCKTABLE where GOODSNUMBER=:C
Первое из SQL-предложений добавляет запись в таблицу STOCKTABLE с автоматической генерацией первичного ключа. Второе удаляет запись на основе значения первичного ключа.
Обратите внимание: ни компонент TTable, ни компонент TProvider не следует экспортировать из модуля данных. Причина этого заключается в том, что подобные экспортированные объекты хранят состояние данных, с которыми работает конкретное клиентское приложение, поэтому при коллективном использовании таких объектов могут возникнуть коллизии. По этой причине сведения о состоянии данных для конкретных клиентов хранятся менеджером разделяемых свойств MTS (MTS shared property manager), а в модулях данных между вызовами методов эти сведения присутствовать не должны. Поэтому вместо экспорта объектов из модуля данных мы создадим метод GetGoods, предоставляющий эти данные клиентскому приложению по его запросу.
После этого можно отредактировать библиотеку типов. Добавим к ней методы GetGoods для передачи клиентскому приложению содержимого таблицы и методы AddGoods и DeleteGoods для выполнения запросов, содержащихся в компонентах TQuery (рис. 12):
Рис. 12. Библиотека типов серверного объекта
Реализация созданных методов приведена ниже:
unit st1; //Simple MTS server //By N.Elmanova //01.12.1998 interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComServ, ComObj, VCLCom, StdVcl, BdeProv, BdeMts, DataBkr, DBClient, MtsRdm, Mtx, st_TLB, DBTables, Provider, Db; type TStockDM1 = class(TMtsDataModule, IStockDM1) stable: TTable; StProvider: TProvider; Database1: TDatabase; Query1: TQuery; Query2: TQuery; Session3: TSession; private { Private declarations } public { Public declarations } protected function GetGoods: OleVariant; safecall; procedure AddGoods(const Gname: WideString; Gprice: Double); safecall; procedure DeleteGoods(Gnumber: Integer); safecall; end; var StockDM1: TStockDM1; implementation {$R *.DFM} function TStockDM1.GetGoods: OleVariant; begin Result:=StProvider.Data; SetComplete; end; procedure TStockDM1.AddGoods(const Gname: WideString; Gprice: Double); begin try Database1.Open; Query1.Params[0].Value:=Gname; Query1.Params[1].Value:=Gprice; Query1.Prepare; Query1.ExecSQL; Database1.Сlose; SetComplete; except SetAbort; raise; end; end; procedure TStockDM1.DeleteGoods(Gnumber: Integer); begin try Database1.Open; Stable.open; Stable.SetRangeStart; Stable.Fields[2].AsInteger:=Gnumber; Stable.SetRangeEnd; Stable.Fields[2].AsInteger:=Gnumber; Stable.ApplyRange; Stable.Delete; Database1.Close; SetComplete; except SetAbort; raise; end; end; initialization TComponentFactory.Create(ComServer, TStockDM1, Class_StockDM1, ciMultiInstance, tmApartment); end.
Прокомментируем приведенный выше код. Напомним, что мы не экспортировали компонент TProvider или компонент TTable из модуля данных, а вместо этого создали метод GetGoods, предоставляющий клиентскому приложению данные из таблицы динамически, позволяя не хранить сведения о состоянии данных в серверном объекте. Метод GetGoods представляет собой так называемый "stateless code", а сам модуль данных в этом случае представляет собой так называемый "stateless object" (об этом было рассказано выше). Именно отсутствие статических данных, связанных с конкретным клиентом, позволит в дальнейшем сделать этот модуль данных разделяемым ресурсом.
Вызов метода SetComplete внутри метода GetGoods означает, что модуль данных более не нуждается в хранении информации о состоянии и может быть деактивирован. Если модуль данных представляет собой одну из частей распределенной транзакции (пока это не так, но чуть позже он станет одной из таких частей), этот метод означает, что данная часть транзакции может быть завершена (естественно, при условии, что все другие части этой транзакции также могут быть завершены; в противном случае произойдет откат транзакции, в том числе и данной части). Если же MTS начинает транзакцию автоматически при создании модуля данных (опция Requires a transaction), вызов метода SetComplete приведет к попытке ее завершения.
Перед компиляцией проекта рекомендуется убедиться, что компоненты TDatabase, TSession, TTable неактивны.
Далее следует выбрать из меню Delphi опцию Run/Install MTS Objects. После этого следует выбрать или ввести имя "пакета" MTS (MTS package). После этого объект окажется зарегистрированным в MTS (рис. 13):
Рис. 13. Серверный объект StockDM1, зарегистрированный в MTS
Следует обратить внимание на то, что регистрировать серверный объект как обычный COM-сервер не следует - в роли сервера с точки зрения реестра для клиента в данном случае выступает MTS, а не созданная библиотека.
При попытках внесения неоднократных изменений в код серверного объекта и запуска сервера с помощью собственно MTS или обращающихся к нему клиентов могут возникнуть проблемы. В частности, может оказаться, что при попытке компиляции библиотеки появляется сообщение о невозможности создания выходного файла. Это может быть связано с тем, что какие-то экземпляры объекта уже созданы в адресном пространстве MTS, поэтому файл оказался заблокированным. В этом случае следует в MTS Explorer найти соответствующий "пакет" и из его контекстного меню выбрать опцию Shut down. Можно также выбрать в MTS Explorer раздел My Computer и из его контекстного меню выбрать опцию Shut down server processes, прекратив таким образом существование всех серверных объектов. Кроме того, можно уменьшить время существования серверного объекта в неактивном состоянии. Это делается с помощью выбора пункта контекстного меню Properties соответствующего пакета и установкой необходимого значения свойства Shut down after being idle for… на странице Advanced появившейся диалоговой панели.
Зарегистрировав созданный серверный объект в MTS, можно приступить к созданию клиентского приложения. Добавим в имеющуюся программную группу новый проект (или просто создадим новый проект). На главную форму будущего приложения поместим компоненты TDCOMConnection, TClientDataSet, TDataSourse, TDBGrid, два компонента TEdit, два компонента TLabel и три кнопки (рис. 14)
Рис. 14. Клиентское приложение для тестирования серверного объекта
В качестве свойства ServerName компонента TDCOMConnection выберем имя только что созданного нами серверного объекта (оно будет доступно, если объект зарегистрирован в MTS, и при его выборе свойство GUID будет установлено автоматически). Если же клиент разрабатывается на удаленном компьютере, следует заполнить свойства GUID и ComputerName, причем в качестве свойства GUID следует выбирать не идентификатор сервера, а идентификатор соответствующего класса объектов - так называемый CoClass GUID. Причина этого очевидна - в общем случае динамически загружаемая библиотека может содержать несколько классов серверных объектов.
Свяжем компонент TClientDataSet с компонентом TDCOMConnection, выбрав его свойство RemoteServer из единственной позиции выпадающего списка. Свойство ProviderName оставим пустым - ведь при создании сервера мы не экспортировали никаких объектов. Далее свяжем компонент TDataSource с компонентом TClientDataSet, и, наконец, свяжем компонент TDBGrid с компонентом TDataSource. Убедимся, что все невизуальные компоненты неактивны - до возникновения реальной необходимости получить какие-либо данные серверные объекты не должны быть созданы, поэтому установка свойств Active или Connected должна быть произведена на этапе выполнения.
Закончив проектирование формы, создадим обработчики событий, связанных с нажатием на кнопки:
unit stscl1; //Client of Simple MTS server //By N.Elmanova //01.12.1998 interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Db, DBClient, MConnect, Grids, DBGrids, StdCtrls; type TForm1 = class(TForm) Button1: TButton; DBGrid1: TDBGrid; DCOMConnection1: TDCOMConnection; ClientDataSet1: TClientDataSet; DataSource1: TDataSource; Button2: TButton; Button3: TButton; Label2: TLabel; Label1: TLabel; Edit1: TEdit; Edit2: TEdit; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); begin DCOMConnection1.Connected:=True; ClientDataSet1.Data:=DCOMConnection1.AppServer.GetGoods; end; procedure TForm1.Button2Click(Sender: TObject); begin try DCOMConnection1.AppServer.AddGoods(Edit1.Text, StrToInt(Edit2.Text)); Except ShowMessage('Не могу добавить запись'); end; end; procedure TForm1.Button3Click(Sender: TObject); var recnum:integer; begin recnum:=ClientDataSet1.FieldByName('GOODSNUMBER').Value; try DCOMConnection1.AppServer.DeleteGoods(recnum); except ShowMessage('Не могу удалить запись'); end; end; end.
Запустив клиентское приложение, протестируем сервер, попытавшись добавить или удалить записи (заодно проверим правильность текста созданных нами SQL-запросов). Обратите внимание: для контроля изменений в базе данных следует нажимать на кнопку Connect & Refresh - обработчик соответствующего события вызывает серверный метод, выполняющий выгрузку данных из таблицы и передачу их клиентскому приложению (рис. 15):
Рис. 15. Тестирование серверного объекта
<< Назад | Содержание | Вперед >>