Модель briefcase засобами MIDAS h2>
Михайло Голованов p>
У минулому номері журналу я писав про реалізації
моделі Briefcase за допомогою ADO. На відміну від ADO, засоби роботи з
відключеними наборами даних у MIDAS були реалізовані з найпершої версії.
Реалізація моделі Briefcase засобами MIDAS дещо простіше, хоча і тут є
свої підводні камені. p>
Додаток MIDAS складається з двох частин: сервера
додатків і тонкого клієнта. Сервер додатків «спілкується» з сервером БД (або
іншим джерелом даних), реалізує бізнес-правила і надає тонкому
клієнту дані за запитом. Функція тонкого клієнта - це відображення даних
користувачеві. p>
Як приклад ми спроектуємо простий сервер
додатків і тонкий клієнт для роботи з базою, описаної в попередньої
частині статті. Сервер додатків реалізуємо як модуль MTS/COM +. Ніяких
особливостей створення сервера додатків для моделі briefcase немає, тому я не
буду описувати цей процес у деталях. У IDE Delphi створимо новий проект
програми для MTS/COM +, вибравши пункт меню File/New/Other, і в що з'явилося,
діалозі виберемо пункт ActiveX Library із закладки ActiveX. Потім включимо в
проект новий видалений модуль даних (File/New/Other і пункт Transactional
Datamodule із закладки Multitier). P>
У віддаленому модулі даних розмістимо наступні
компоненти і встановимо значення їх властивостей згідно з таблицею 1, наведеною
нижче. p>
Компонент p>
Властивість p>
Значення p>
Conn: TADOConnection p>
ConnectionString p>
Налаштувати на з'єднання з
БД p>
adsParams: TADODataSet p>
Connection p>
Conn p>
CommandText p>
CommandText p>
select * from Params p>
dspParams: TDataSetProvider p>
DataSet p>
adsParams p>
Таблиця 1 p>
Відкомпілюйте проект сервера і встановіть отриману
dll в нове MTS/COM +-додаток c ім'ям MIDAS_briefcase, за допомогою пункту меню
Run/Install COM + Object. P>
Тонкий клієнт являє собою DeskTop-додаток.
Для зв'язку з сервером додатків клієнт використовує один з компонентів типу
Connection із закладки DataSnap (ми скористаємося DCOMConnection) та спеціальний
DataSet - ClientDataSet. P>
У таблиці 2 наведені властивості, що впливають на роботу
компонентів тонкого клієнта з сервером додатків, а також їх значення. p>
Компонент p>
Властивість p>
Значення p>
DCOMConn: TDCOMConnection p>
ServerName p>
Project1.MIDAS_bc p>
LoginPrompt p>
false p>
cdsParams: TClientDataSet p>
RemoteServer p>
DcomConn p>
ProviderName p>
dspParams p>
Таблиця 2 p>
Зовнішній вигляд форми програми тонкого клієнта наведено
на малюнку 1. p>
p>
Малюнок 1 p>
Отримання даних з центрального сервера
p>
Отримання даних з сервера додатків аналогічно
наведеним у прикладі для ADO. Код наведено нижче. P>
procedure TForm1.act_RemoteConnectExecute (Sender: TObject); p>
begin p>
try p>
with cdsParams do try p>
Close; p>
RemoteServer: = DCOMConn; p>
FileName :=''; p>
Active: = true; p>
except on E: Exception do p>
MessageDlg (Format ( 'Помилка підключення до сервера:% s', [E. Message]), p>
mtError, [mbOk], 0); p>
end; p>
finally p>
DCOMConn.Close; p>
end; p>
Все просто, але для підключення до сервера треба не
забувати скидати назва файлу кеша (властивість FileName) і закривати з'єднання з
сервером після отримання даних. p>
Читання та запис даних з локального кеша
p>
Для читання даних з локального кеша у ClientDataSet
є метод LoadFromFile: p>
procedure TForm1.act_ConnectLocalExecute (Sender: TObject); p>
begin p>
with cdsParams do p>
begin p>
RemoteServer: = nil; p>
FileName: = ExtractFilePath (Application.ExeName)
+ LocalFile; p>
LoadFromFile (FileName); p>
end; p>
end; p>
Для запису до локальний кеш - метод SaveToFile: p>
with cdsParams do p>
SaveToFile (ExtractFilePath (Application.ExeName)
+ LocalFile); p>
Знову ж ніяких сюрпризів. p>
Збереження даних на сервер, скасування зроблених
змін
p>
Для збереження даних на сервер призначений метод
CilentDataSet-а ApplyUpdates. Параметр даного методу вказує макісімально
допустиму кількість помилок при передачі даних на сервер. p>
procedure
TForm1.act_SaveToServerExecute (Sender: TObject); p>
begin p>
if cdsParams.Active and
(cdsParams.ApplyUpdates (0) = 0) then p>
act_RemoteConnect.Execute; p>
end; p>
При виникненні помилки генерується подія
OnReconcileError. Обробка помилки здійснюється за допомогою стандартного модуля
обробника помилки, підключити цей модуль в проект можна, вибравши
ReconcileErrorDialog на закладці Dialogs (File/New/Other). Оброблювач
OnReconcileError буде виглядати при цьому такий спосіб: p>
procedure
TForm1.cdsParamsReconcileError (DataSet: TCustomClientDataSet; p>
E: EReconcileError; UpdateKind: TUpdateKind; p>
var Action: TReconcileAction); p>
begin p>
Action: = HandleReconcileError (DataSet,
UpdateKind, E); p>
end; p>
Для скасування внесених змін ClientDataSet містить
метод CancelUpdates. p>
Читання даних "порціями"
p>
Обмін даних між тонким клієнтом і сервером
додатків відбувається пакетами. Пакет містить інформацію про метадані та/або
набір записів. Цікавою можливістю MIDAS є передача даних
«Порціями». Природно, ця можливість реалізується лише під час наявності
з'єднання з центральним сервером. Включення даної можливості здійснюється
установкою властивості FetchOnDemand ClientDataset-а в true. На жаль, навіть при
цьому запит до сервера бази даних вибирає всі записи відразу. Про один із
способів усунення цього недоліку я й хочу розповісти. p>
Перше, що необхідно зробити, це змінити текст
SQL-запиту, що знаходиться у властивості CommandText компонента adsParams (нагадаю,
що це ADODataSet), на наступний: p>
select
Top 2 * from Params where ParamID>: ID p>
При цьому записи будуть вибиратися порціями по 2.
Природно, що в реальних додатках розмір пакета буде більше цього числа.
Конкретне число являє собою компроміс між витратами часу на
передачу пакета по мережі (чим більше розмір пакету, тим вище затримки при його
передачі) і затримками, що виникають від частого звернення із запитами до сервера
БД. (на практиці розмір такого пакету залежить від розміру запису, але в середньому
становить від 30 до 250 рядків. - Прим.ред.) P>
Залишається вирішити проблему, як клієнт буде передавати
серверу ідентифікатор останньої зчитаної записи (ParamID). Проблема ця
виникає тому, що сервер додатків не зберігає стан між викликами
клієнтів. З одного боку, це робить сервер додатків більш масштабованим,
але з іншого, вимагає додаткового коду на стороні клієнта для зберігання
стану. p>
Для передачі значення первинного ключа останньої
прочитаної клієнтом записи можна використовувати подія BeforeGetRecords. Дане
подія визначено для набору даних ClientDataSet на клієнті і компонента
провайдера на сервері. Перед отриманням пакету із записами цю подію спочатку
генерується на клієнта, а потім у провайдера на сервері додатків. Подія
має параметр, який передається за посиланням OwnerData (типу OleVariant). Ось через
цей параметр ми і будемо передавати значення ключа останнього запису. Код
обробника в клієнтському додатку: p>
procedure
TForm1.cdsParamsBeforeGetRecords (Sender: TObject; p>
var OwnerData: OleVariant); p>
begin p>
if not
cdsParams.FieldByName ( 'ParamID'). IsNull then p>
OwnerData: = cdsParams.FieldByName ( 'ParamID'). Value; p>
end; p>
Відповідно для провайдера на сервері додатків: p>
procedure
TMIDAS_bc.dspParamsBeforeGetRecords (Sender: TObject; p>
var OwnerData: OleVariant); p>
begin p>
if not VarIsNull (OwnerData) and not
VarIsClear (OwnerData) then p>
adsParams.Parameters.ParamValues [ 'ID']: = OwnerData; p>
end; p>
Щоб ініціювати підкачки, необхідно обробляти
ще одна подія об'єкта ClientDataSet - OnScroll: p>
procedure
TForm1.cdsParamsAfterScroll (DataSet: TDataSet); p>
begin p>
if cdsParams.Eof then p>
cdsParams.GetNextPacket; p>
end; p>
Виклик cdsParams.GetNextPacket; якраз і ініціює
отримання наступного пакета даних. p>
Як з'ясувалося, код методу GetNextPacket містить
помилку, що виражається у відмові читання наступного пакета після кількох спроб
читання першого пакету. Пов'язано це з неправильним значенням змінної
ProviderEOF в описаній вище ситуації. P>
function
TCustomClientDataSet.GetNextPacket: Integer; p>
begin p>
CheckActive; p>
if ProviderEOF then Result: = 0 else p>
begin p>
UpdateCursorPos; p>
if (FPacketRecords = 0) and
FMasterLink.Active and p>
(FMasterLink.Fields.Count> 0) then
CheckDetailRecords else p>
begin p>
AddDataPacket (DoGetRecords (FPacketRecords,
Result, 0,'', Unassigned), p>
Result <> FPacketRecords); p>
ProviderEOF: = Result <>
FPacketRecords; p>
end; p>
Resync ([]); p>
end; p>
end; p>
Виправлено це було явним присвоєнням ProviderEOF
значення false перед виконанням перевірки if ProviderEOF then Result: = 0 else
... ... P>
Вищеописаний метод організації підкачки має, до
жаль, один великий недолік - він буде працювати лише під час сортування
набору даних на клієнті по первинному ключу. Однак не так складно доопрацювати
його для обходу і цієї проблеми. p>
Список літератури h2>
Для підготовки даної роботи були використані
матеріали з сайту http://www.rsdn.ru/
p>