ПЕРЕЛІК ДИСЦИПЛІН:
  • Адміністративне право
  • Арбітражний процес
  • Архітектура
  • Астрологія
  • Астрономія
  • Банківська справа
  • Безпека життєдіяльності
  • Біографії
  • Біологія
  • Біологія і хімія
  • Ботаніка та сільське гос-во
  • Бухгалтерський облік і аудит
  • Валютні відносини
  • Ветеринарія
  • Військова кафедра
  • Географія
  • Геодезія
  • Геологія
  • Етика
  • Держава і право
  • Цивільне право і процес
  • Діловодство
  • Гроші та кредит
  • Природничі науки
  • Журналістика
  • Екологія
  • Видавнича справа та поліграфія
  • Інвестиції
  • Іноземна мова
  • Інформатика
  • Інформатика, програмування
  • Юрист по наследству
  • Історичні особистості
  • Історія
  • Історія техніки
  • Кибернетика
  • Комунікації і зв'язок
  • Комп'ютерні науки
  • Косметологія
  • Короткий зміст творів
  • Криміналістика
  • Кримінологія
  • Криптология
  • Кулінарія
  • Культура і мистецтво
  • Культурологія
  • Російська література
  • Література і російська мова
  • Логіка
  • Логістика
  • Маркетинг
  • Математика
  • Медицина, здоров'я
  • Медичні науки
  • Міжнародне публічне право
  • Міжнародне приватне право
  • Міжнародні відносини
  • Менеджмент
  • Металургія
  • Москвоведение
  • Мовознавство
  • Музика
  • Муніципальне право
  • Податки, оподаткування
  •  
    Бесплатные рефераты
     

     

     

     

     

     

         
     
    MIDAS. Практичне застосування
         

     

    Інформатика, програмування

    MIDAS. Практичне застосування

    Роман Ігнатьєв

    Введення

    Необхідні знання: Перед прочитанням рекомендується ознайомитися з технологією MIDAS хоча б на рівні демонстраційних програм, що поставляються з Delphi.

    Технологія MIDAS (Multi-tier Distributed Application Services Suite, Сервіс для створення багаторівневих додатків) була запропонована фірмою Borland вже досить давно, перший додаток з її використанням я написав ще в 98 році, на Delphi 4. І з тих пір практично всі програми для роботи з базами даних створюються мною саме на основі MIDAS. Про переваги, думаю, говорити не треба - навіть просте розділення програми на дві частини, одна з яких працює з базою даних (сервер додатків), а інша забезпечує інтерфейс користувача, створює значні зручності як при розробці програми, так і при його використанні.

    Зараз існує безліч статей і книг з технології створення багатоланкових додатків на Delphi, але, на жаль, мені не вдалося знайти літератури, в якій би розглядалися деякі питання, що цікавлять мене питання. Справа в тому, що у всіх прикладах створюється триланкового додаток, в якому сервер додатків просто з'єднує сервер БД з клієнтською частиною, просто абстрагуючись роботу з базою даних.

    З одного боку, це дає деяку перевагу при переході з дворівневої технології (клієнт-сервер) на трирівневу, для чого компоненти доступу до бази даних з клієнтської частини переносяться в сервер додатків. З іншого боку, хочеться більшого, а саме перенесення на сервер не тільки роботи з таблицями бази даних, але й основний логіки програми, залишаючи клієнтської частини тільки завдання взаємодії з користувачем.

    Нижче розглядається дуже простий додаток для роботи з сервером БД. База даних, з якою працює це додаток, що містить всього 3 таблиці. З його допомогою мені хотілося б показати деякі способи створення повноцінного сервера додатків, що забезпечує повну обробку даних.

    Все, що написано нижче, відноситься до Delphi 5, в Як сервер обраний Interbase 5.6. Саме ці продукти я використовував у більшості проектів. Проте база даних працює і на більш старших версіях Interbase, я перевіряв її працездатність, зокрема, на IB6, а вихідні тексти додатки з мінімальними змінами можна компілювати на старших версіях Delphi. На жаль, деякі зміни робити все ж таки доведеться, так як MIDAS постійно розвивається. Але, як правило, такі зміни мають косметичний характер, і зробити їх нескладно. Як змінити проект для компіляції на Delphi 6, буде розказано в заключній частині. Незважаючи на те, що в сервері додатків використовуються компоненти прямого доступу Interbase Express (IBX), неважко перейти на інший сервер БД, просто замінивши компоненти доступу і трохи змінивши текст методів.

    Виходячи з практичних міркувань нижче описано створення трирівневого додатки, включаючи розробку структури бази даних. Мені не хотілося користуватися готовими прикладами, що входять в поставку Interbase і Delphi, щоб не дотримуватися шаблонів, які нав'язує ними.

    Постановка завдання

    Я збираюся на простому прикладі показати можливості технології, тому програма буде працювати лише з одним логічним об'єктом - Деяким абстрактним документом, що складається з заголовка і вмісту. Така структура була обрана з-за того, що зв'язок "майстер-таблиця - підлегла таблиця "(master-detail) дуже часто зустрічається на практиці, і також часто в додатках зустрічається обробка різних документів.

    "Документ" тут розуміється як деяка абстракція, нагадує реальні документи, такі, як накладна або рахунок.

    Зрозуміло, необхідно забезпечити одночасну роботу з документами декількох користувачів.

    Опис документа

    Заголовок документа простий, і складається з наступних полів:

    Номер - номер документа.

    Дата - дата виписки, за замовчуванням поточна.

    Постачальник - ім'я, телефон.

    Одержувач - також ім'я і телефон.

    Сума - загальна сума по документу.

    Постачальник і одержувач будуть вибиратися з єдиного довідника клієнтів, що містить лише два поля: ім'я і телефон.

    Документ містить записи з відомостями про переміщення деяких товарів. Вміст документа - це таблиця у базі даних, записи якої складаються з наступних полів:

    Порядковий номер

    Номер документа, до якого належить.

    Найменування - найменування товару.

    Кількість.

    Ціна.

    Сума.

    Результат може виглядати наступним чином:        

    номер п/п         

    найменування         

    кількість         

    ціна         

    сума             

    1         

    Щось         

    1         

    30.00         

    30.00             

    2         

    Что-то еще         

    2         

    10.50         

    21.00             

    ...                                         

    Користувачеві повинен видаватися перелік документів з зазначенням реквізитів документа і його підсумкової суми.

    Сума документа буде розраховуватися сервером додатків на основі його вмісту.

    Крім цього, створимо загальний звіт -- "шахову" відомість, де по одній осі розташовані постачальники, а по другий - одержувачі:        

    За період з <дата> по   <дата> (за датою документа)             

    Від кого         

    Кому                      

    Получатель1 (ім'я)         

    Получатель2 (ім'я)         

    ...             

    Поставщік1 (ім'я)         

    Сума         

    Сума                      

    Поставщік2 (ім'я)         

    Сума         

    Сума                      

    ...                                

    До звіту будуть входити суми з документів, дати яких потрапляють у заданий період.

    Звіт, зрозуміло, виходить також досить відірваним від життя, мені просто хотілося показати з його допомогою додаткові можливості MIDAS.

    Блокування

    Оскільки працювати з БД будуть відразу кілька користувачів, важливо заблокувати документ на час редагування документа користувачем. "Почесна обов'язок" синхронізацію роботи користувачів покладається у даному випадку на сервер додатків. У принципі, все це можна реалізувати і засобами SQL сервера, але, по-перше, для сервера Interbase блокування записів досить неприродні, по-друге, як буде видно нижче, сервер додатків дозволяє легко блокувати відразу весь документ як єдиний об'єкт.

    Структура БД

    Вимоги до додатка описані, і тепер можна приступити до наступного етапу - створення БД. Нам потрібні три таблиці (магічне число три вибрано виключно для простоти): для документа, вмісту документа і довідника клієнтів. Документ містить два посилання на довідник клієнтів - "Постачальник" і "Одержувач", а вміст документа має посилання на документ. На малюнку 1 представлена ER-діаграма цієї БД.

    Малюнок 1. ER-модель БД.

    У даній базі цілісність даних забезпечується за наступних правил:

    При згадуванні в будь-якому документі постачальника або одержувача видалення цього рядка з довідника клієнтів не допускається.

    Після видалення документа видаляються і всі пов'язані з ним рядки з таблиці "Вміст документа".

    Вставка до таблиці "Вміст документа" допускається тільки за умови, що поле ID посилається на існуючий документ.

    Як видно, в таблиці "Заголовок документа" є поле "Сума". Це поле повинно містити повну суму документа (суму полів "Сума" вмісту документа). При зміні вмісту документа доводиться перераховувати значення цього поля. Наявність такого поля, є порушенням принципів нормалізації. Цю суму можна завжди порахувати в SQL-запиті при виведенні даних користувачеві. Але при видачі списку документів розрахунок суми кожного з них збільшує навантаження на сервер БД. Відстежувати актуальність цього поля можна на тригерах СУБД, але раз вже ми створюємо сервер додатків, чому б не покласти на нього це завдання? До того ж, це забезпечує певну незалежність від особливостей функціонування сервера БД, що може виявитися корисним, наприклад, при переході на інший сервер.

    Нижче наведено скрипт, який створює піддослідних БД:        

    /* Визначено такі типи   даних:   

    Ім'я клієнта   

    Кількість для вмісту документа   

    Ціна   

    Сума   

    */      

    create domain DName varchar (180);   

    create domain DCount numeric (15,4) default 1 not null;   

    create domain DCurrency numeric (15,2) default 0 not null;   

    create domain DSum numeric (15,4) default 0 not null;      

    /* Довідник постачальників і   одержувачів */      

    create table CLIENT   

    (   

    CLIENT_ID integer not null,   

    NAME DName not null,/* Ім'я */   

    PHONE varchar (40),/* Телефон */      

    constraint PK_CLIENT primary   key (CLIENT_ID)   

    );      

    /* Заголовок документа */      

    create table DOC_TITLE   

    (   

    DOC_ID integer not null,/* ID */   

    DOC_NUM varchar (40) not   null,/* Номер */   

    DOC_DATE date not null,/* Дата */   

    FROM_ID integer default 0 not   null,/* Постачальник */   

    TO_ID integer default 0 not   null,/* Одержувач */   

    DOC_SUM DSum,/* Сума */      

    constraint PK_DOC_TITLE primary   key (DOC_ID),   

    constraint FK_DOC_FROM_CLIENT   foreign key (FROM_ID)   

    references Client (CLIENT_ID)   

    on update cascade,   

    constraint FK_DOC_TO_CLIENT   foreign key (TO_ID)   

    references Client (CLIENT_ID)   

    on update cascade   

    );      

    /* Вміст */      

    create table DOC_BODY   

    (   

    DOC_ID integer not null,/* сcилка на заголовок */   

    LINE_NUM integer not null,/* Номер п/п */   

    CONTENT varchar (250) not null,   / * Найменування */   

    COUNT_NUM DCount,/* Кількість */   

    PRICE DCurrency,/* Ціна */      

    constraint PK_DOC_BODY primary   key (DOC_ID, LINE_NUM),   

    constraint FK_DOC_BODY_TITLE   foreign key (DOC_ID)   

    references DOC_TITLE (DOC_ID)   

    on delete cascade   

    on update cascade   

    );     

    Скрипт створює три таблиці: CLIENT (постачальники/одержувачі), DOC_TITLE (документ), DOC_BODY (зміст документа).

    Наступний етап - формування списку документів. У заголовку документа міститься тільки посилання на постачальника і одержувача. Висновок списку зручно організувати окремим запитом, а в даному випадку - що зберігається процедурою. Нехай для зручності ім'я клієнта в списку відображається у вигляді "Ім'я (Телефон)". Для цього зробимо процедуру CLIENT_FULL_NAME, яка витягує цей рядок, і будемо викликати її з процедури видачі списку LIST_DOC. Ця ж процедура стане в нагоді для відображення імені постачальника і одержувача на формі редагування документа:        

    create procedure CLIENT_FULL_NAME (ID integer)   

    returns (FULL_NAME varchar (224))   

    as   

    declare variable NAME   varchar (180);   

    declare variable PHONE   varchar (180);   

    begin   

    select NAME, PHONE   

    from client   

    where CLIENT_ID =: ID   

    into: NAME,: PHONE;   

    FULL_NAME ='';   

    if (NAME is not NULL) then   

    FULL_NAME = NAME;   

    if (PHONE is not NULL) then   

    FULL_NAME = FULL_NAME | | '('   | | PHONE | |')';   

    end   

    create procedure LIST_DOC (FROM_DATE date, TO_DATE date)   

    returns (DOC_ID integer, DOC_NUM varchar (40), DOC_DATE date, FROM_ID   integer,   

    TO_ID integer, FROM_NAME   varchar (224), TO_NAME varchar (224),   

    DOC_SUM numeric (15,4))   

    as   

    begin   

    for select DOC_ID, DOC_NUM,   DOC_DATE, FROM_ID, TO_ID, DOC_SUM   

    from DOC_TITLE   

    where DOC_DATE> =   : FROM_DATE and DOC_DATE <=: TO_DATE   

    into: DOC_ID,: DOC_NUM,   : DOC_DATE,: FROM_ID,: TO_ID,: DOC_SUM   

    do begin   

    FROM_NAME = NULL;   

    TO_NAME = NULL;      

    execute procedure   CLIENT_FULL_NAME (: FROM_ID)   

    returning_values: FROM_NAME;      

    execute procedure   CLIENT_FULL_NAME (: TO_ID)   

    returning_values: TO_NAME;      

    suspend;   

    end   

    end     

    Залишилася процедура для звіту:        

    create procedure REP_INOUT (FROM_DATE date, TO_DATE date)   

    returns (FROM_ID integer, FROM_NAME varchar (180), TO_ID integer,   TO_NAME varchar (180),   

    FULL_SUM numeric (15,4))   

    as   

    begin   

    for select FROM_ID, TO_ID,   sum (DOC_SUM)   

    from DOC_TITLE   

    where DOC_DATE> =   : FROM_DATE and DOC_DATE <=: TO_DATE   

    group by FROM_ID, TO_ID   

    into: FROM_ID,: TO_ID,   : FULL_SUM   

    do begin   

    FROM_NAME = NULL;   

    TO_NAME = NULL;      

    select NAME   

    from client   

    where CLIENT_ID =: FROM_ID   

    into: FROM_NAME;      

    select NAME   

    from client   

    where CLIENT_ID =: TO_ID   

    into: TO_NAME;      

    if (FULL_SUM is NULL) then   

    FULL_SUM = 0;   

    suspend;   

    end   

    end     

    Процедура видає те, що потрібно для звіту, але, на жаль, не у вигляді перехресного звіту, а за рядками:        

    Від кого         

    Кому         

    На суму             

    <Постачальник>         

    <Одержувач>         

    Сума ...             

    ...                       

    Приводити до нормального вигляду все це буде сервер додатків.

    Все готово для написання сервера додатків. Розпочнемо.

    Сервер додатків

    Створюваний нами сервер додатків буде окремим виконуваним модулем. Цей модуль потім можна буде розташувати на окремому комп'ютері, який зможе проводити розрахунки для декількох клієнтів і синхронізувати їх роботу.

    Сервер додатків повинен забезпечувати обробку документа як єдиного об'єкта, тому розумним буде виділити роботу з ним в окремий клас, в даному випадку нащадок TRemoteDataModule. Нам також знадобиться модуль даних для роботи з довідником постачальників і одержувачів, і видачі списку документів. Звіт я вирішив також виділити в окремий модуль. У підсумку на сервер необхідно створити три нащадка TRemoteDataModule: rdmCommon (загальний модуль зі списками постачальників/одержувачів та документів), rdmDoc і rdmReport - відповідно для документа і звіту.

    Майстер створення віддаленого модуля даних пропонує по замовчуванням політику завантаження виконуваного модуля Multiple instance і модель потоків Apartment. Це саме те, що нам треба! Дійсно, Instancing = Internal призведе до створення серверного компонента в клієнтському процесі (це поширюється тільки на сервер, який створюється у вигляді DLL). При Single instance кожна клієнтська частина буде з'єднуватися зі своїм власним примірником сервера додатків, а синхронізацію простіше зробити, якщо всі клієнти приєднуються до одному примірнику сервера додатків. Вибір моделі потоків Apartment дозволить уникнути ручної синхронізації доступу до даних компонента.

    Тепер залишається створити три (знову три, це теж випадково) нащадка TRemoteDataModule, розташувати на них компоненти доступу до даними і написати код для обробки даних.

    При цьому необхідно враховувати, що при використанні моделі потоків Apartment кожен модуль даних працює у своєму потоці, і тому в кожному модулі повинен знаходиться окремий компонент TIBDatabase.

    При прямому доступі провайдера до бази (властивість ResolveToDataset = false) MIDAS також вимагає наявності окремої копії об'єкта TIBTransaction для кожного компонента доступу до даних, тобто у кожного провайдера повинна бути своя транзакція. Компонент TIBTransaction специфічний для компонентів прямого доступу до Interbase, звичайно робота з транзакціями покладено на компонент з'єднання з базою даних.        

    ПРИМІТКА   

    При використанні сервера Interbase для   доступу до даних за технологією MIDAS логічно використовувати IBX, провайдери   даних чудово працюють з цими компонентами. Єдине зауваження --   Borland сертифікувала на момент написання статті версію IBX 4.52. Більше   пізні версії працюють у складі MIDAS трохи інакше, ніж раніше. У   Зокрема, транзакції тепер не закриваються автоматично після вибірки   даних.     

    Розглянемо віддалені модулі даних по порядку, і почнемо з модуля довідників (rdmCommon) (малюнок 2).

    Малюнок 2. Загальний модуль rdmCommon.

    Компонент ibqDocs має тип TIBDatabase і забезпечує підключення модуля з сервером БД. У мене БД знаходиться в каталозі d: projectsdocmidasdata і називається doc.gdb. У що додається до статті проект сервер додатків дозволяє вказати довільне місцезнаходження сервера БД і файлу бази даних.

    Для того, щоб при кожному з'єднанні сервер додатків не запитував ім'я користувача і пароль, вони просто вказані в параметрах з'єднання. Ім'я користувача SYSDBA та пароль masterkey є установками за замовчуванням при інсталяції сервера Interbase.

    Перерахуємо компоненти модуля. До компоненту транзакції ibtClient приєднаний запит ibqClient (компонент TIBQuery), до якого, у свою чергу, приєднаний провайдер dspClient. Відповідно, у транзакції і запиту зазначено з'єднання з БД ibdDocs. Залишається тільки встановити тип транзакції read committed (зручніше за все це зробити, двічі клацнувши на відповідному компоненті, і вибравши його тип), і у властивості SQL-запиту записати "select * from client". Тепер провайдер може надавати клієнтської частини можливість працювати з довідником клієнтів. Але для підвищення комфорту потрібно додати можливість декільком користувачам змінювати одночасно різні поля в одній і тій же записи в таблиці (їх два: Name і Phone). Робиться це досить просто, в редакторі полів (Fields Editor) ibqClient потрібно створити постійна список всіх полів запиту, і в поля CLIENT_ID в його властивість ProviderFlags додати опцію pfInKey. Потім у провайдера dspClient встановити властивість UpdateMode в upWhereChanged. У цьому випадку, якщо різні клієнтські частини змінять різні поля о?? ної записи в таблиці CLIENT, сервер додатків прийме ці зміни. У випадку, якщо будуть змінені одні й ті ж поля запису, клієнтської частини буде видане повідомлення виду «Запис змінена іншим користувачем».        

    ПРИМІТКА   

    Тут мені хотілося б зупинитися на   властивості TField.ProviderFlags і TDataSetProvider.UpdateMode. Справа в тому, що   мене часто запитують, що залежить від значень цих властивостей, а залежить від них   досить багато. У довідці за VCL ці властивості описані, на мій погляд,   недостатньо докладно, а зв'язок між ними досить тісний. Отже, нехай   є компонент TQuery, TIBQuery або якийсь інший (запит), з'єднаний   з сервером БД, і до нього приєднаний TDataSetProvider. У цьому випадку на логіку   роботи впливають саме значення властивості ProviderFlags полів цього   запиту, аналогічні властивості полів на стороні клієнта ніякого впливу не   роблять. Комбінація значень цих властивостей повністю визначає, як будуть   проводитися операції оновлення даних на сервері БД. Розглянемо оновлення   даних в таблиці. Додавання та видалення запису відбувається аналогічно.   

    Провайдер з встановленим властивістю   ResolveToDataset = false при оновленні запису формує SQL-запит виду   UPDATE

    SET = , ... WHERE    = AND ..., у повній відповідності до стандарту   SQL (при ResolveToDataset = True проводиться пошук і оновлення прямо в таблиці).   

    Ім'я таблиці

    береться з   Dataset (провайдер чудово розуміє запити SQL виду Select from ...),   або задається в обробнику OnGetTableName. Значення NewValue і OldValue для   кожного поля беруться з пакета оновлення, що посилається провайдера. Імена   полів у висловах SET і FROM формуються автоматично, саме на основі   властивостей ProviderFlags і UpdateMode того набору даних, через який   провайдер працює з базою. Алгоритм наступний:   

    У пропозиція SET входять тільки ті   поля, у яких встановлено прапор pfUpdate у властивості ProviderFlags (потрібно   оновлювати в базі даних) і OldValue <> NewValue (значення поля було   змінено).   

    Пропозиція WHERE формується наступним   так:   

    Беруться всі поля, у який встановлені   [pfInKey, pfInWhere], фактично це первинний ключ. При UpdateMode = upWhereKeyOnly   більше ніяких полів не береться.   

    При UpdateMode = upWhereChanged до полів   первинного ключа додаються ті поля, у яких OldValue <> NewValue і   pfWhere in ProviderFlags, що дозволяє робити перевірку на зміну тих же   полів іншим користувачем.   

    При UpdateMode = upWhereAll до списку   полів WHERE входять всі поля запису, у яких pfWhere in ProviderFlags.   

    У випадку, якщо запис в таблиці на   сервері не знайдена (немає записів, що задовольняють умові WHERE) користувачеві   видається повідомлення виду "Запис змінена іншим користувачем", поза   залежно від причини.   

    Залишається одне значення прапора, pfHidden.   Поля з цим прапором не передаються клієнтського додатку, і не приймаються від   нього, прапор вказує, що ці поля - тільки для використання на стороні сервера.     

    Якщо вже створено постійний список полів, можна встановити параметри їх відображення на клієнтської частини, зокрема, DisplayLabel, DisplayWidth і Visible, а у провайдера - прапори poIncFieldProps. При цьому на клієнтської частини можна не турбуватися про список полів - значення, отримані з сервера додатків, перевизначають задані на клієнті в будь-якому випадку. Разом з тим у провайдера треба встановити опцію poMultiRecordUpdates, щоб на клієнтської частини можна було змінювати відразу кілька записів у довіднику до відправки змін на сервер.

    Поле CLIENT_ID в довіднику постачальників та одержувачів є первинним ключем, а отже, в ньому мають міститися унікальні значення. Для отримання унікальних значень зручно використовувати автоінкрементальние поля (autoincrement field). У IB власне автоінкрементним полів немає, наростаючі значення одержують від генератора з допомогою функції Gen_ID, і як правило, привласнюють це значення полю в тригері. Мені подобається ситуація, коли нове унікальне значення з'являється на клієнтської частини відразу після додавання запису. Тому замість присвоєння значення, отриманого від генератора, в тригері, використовується збережена процедура, результатом роботи якої і є це значення. Для цього в віддаленому модулі даних розташований компонент spNewID: TIBStoredProc, приєднаний до компоненту транзакції ibtDefault, який надає доступ до процедури, що зберігаються на сервері БД. Процедура описана в базі даних таким так:        

    create   procedure CLIENT_ID   

    returns   (ID integer)   

    as   

    begin   

    ID = Gen_ID (CLIENT_ID_GEN, 1);   

    end     

    Як видно, процедура просто видає таке значення генератора. Це гарантує, що при послідовних запитах до неї це значення повторюватися не буде. Отримання значення на клієнтської частини забезпечується методом сервера, про це трохи нижче.

    Друга збережена процедура, spClientFullName, приєднана до компоненту транзакції ibtClient і призначена для видачі імені і телефону постачальника або одержувача у вигляді рядка «Ім'я (телефон)», що підлягає поверненню процедурою сервера БД CLIENT_FULL_NAME. Цей рядок також передається на клієнтську частину через метод сервера.

    Група компонентів ibtDocList, ibqDocList, dspDocList і ibqDelDoc призначена для роботи зі списком документів. У IbtDocList, компонента транзакції, встановлено режим read committed, а в компоненті ibqDocList міститься SQL-запит «select * from List_doc (: FromDate,: ToDate)». Весь список документів відразу виводити досить безглуздо, їх може бути багато. Тому запит вибирає список документів, дати яких лежать в проміжку від FromDate до ToDate. Провайдер dspDocList видає цей список клієнтської частини.

    Додатковий компонент, ibqDelDoc, як, думаю, видно з його назви, призначений для видалення документа, в його властивості SQL варто запит «delete from DOC_TITLE where DOC_ID =: DOC_ID». Незважаючи на те, що для створення і зміни документа планується використовувати окремий модуль, rdmDoc, для видалення документа зовсім необов'язково його відкривати, і з точки зору інтерфейсу користувача зручно робити це прямо з списку документів. На перший погляд, використання окремого запиту для видалення здається зайвим, для цього звичайно досить оголосити в обробнику dspDocList.OnGetTableName ім'я таблиці (DOC_TITLE), а відступ буде автоматично забезпечено. Однак у постановці завдання стоїть умова, що відкритий в одній клієнтської частини документ повинен бути недоступний для зміни (а значить, і видалення) з інших клієнтських частин. Тому доводиться робити це в обробнику події dspDocList.OnBeforeUpdateRecord наступним чином:        

    procedure TrdmCommon.dspDocListBeforeUpdateRecord (Sender: TObject;   

    SourceDS: TDataSet; DeltaDS:   TClientDataSet; UpdateKind: TUpdateKind;   

    var Applied: Boolean);   

    var   

    DocID: Integer;   

    begin   

    if UpdateKind = ukDelete then   //Тільки якщо запис видаляється   

    begin   

    DocID: =   DeltaDS.FieldByName ( 'DOC_ID'). AsInteger;   

    try   

    if not RegisterDoc (DocID)   then// Намагаємося зареєструвати   

    raise Exception.Create ( 'Документ редагується');   

    with ibqDelDoc do// Видаляємо   

    begin   

      paramByName ( 'DocID'). AsInteger: = DocID;   

    ExecSQL;   

    end;   

    Applied: = True;   

    finally   

    UnregisterDoc (DocID);// Зміна закінчено, видалили   

    end;   

    end;   

    end;     

    Якщо видаляється документ, спробуємо його зареєструвати в списку редагованих функцією RegisterDoc, потім, якщо це вийшло, видаляємо його з допомогою запиту ibqDelDoc і видаляємо зі списку редагування (UnregisterDoc). Встановлюємо Applied: = true, щоб сказати провайдеру, що все вже зроблено.

    Звичайно, одночасно може редагуватися (вилучатися для того, додаватися) досить багато документів, тому потрібний єдиний список цих документів, до якого будуть звертатися процедури RegisterDoc і UnregisterDoc. Оскільки звернення до нього буде проводитися з модулів даних, що працюють в різних потоках, то найкраще для цього підходить TThreadList (потокобезопасний клас списку). Список документів має бути єдиним для всіх клієнтських частин, тому його потрібно розташувати в окремому модулі, наприклад, в модулі головної форми сервера. На ній потім можна вивести, наприклад, список редагованих на даний момент документів. Так і зробимо.

    У модулі головної форми сервера в розділі implementation оголосимо змінну DocList: TThreadList; Цей список краще ініціалізувати відразу при запуску сервера і знищувати при виході:        

    initialization   

    DocList: = TThreadList.Create;      

    finalization   

    if Assigned (DocList) then   

    begin   

    DocList.Free;   

    DocList: = nil;   

    end;   

    end.     

    З цим списком працюють дві функції: RegisterDoc і UnregisterDoc:        

    function RegisterDoc (DocID: integer): boolean;   

    begin   

    Result: = False;   

    if DocID = 0 then Exit;   

    with DocList.LockList do   

    try   

    if IndexOf (Pointer (DocID))   <0 then   

    begin   

    Add (Pointer (DocID ));   

    Result: = True;   

    end;   

    finally   

    DocList.UnlockList;   

    end;   

    end;      

    function UnregisterDoc (DocID: integer): boolean;   

    begin   

    Result: = False;   

    if DocID = 0 then Exit;   

    with DocList.LockList do   

    try   

    if IndexOf (Pointer (DocID))   > = 0 then   

    begin   

    Remove (Pointer (DocID ));   

    Result: = True;   

    end;   

    finally   

    DocList.UnlockList;   

    end;   

    end;     

    У списку зберігаються ідентифікатори документів. Але TThreadList призначений для зберігання покажчиків. Тому для зберігання в цьому списку ідентифікатора, що має тип Integer, доведеться привести його до типу pointer. Звичайно, якщо буде потрібно зберігати додаткову інформацію про документі, наприклад, його номер, доведеться організувати в списку посилання на запису, з виділенням пам'яті під цей запис і знищенням непотрібних записів. При цьому зовнішній вигляд функцій не зміниться, просто ускладниться робота зі списком, і може знадобитися звернення до БД для отримання додаткової інформації.

    Тепер все просто: всі модулі даних, які працюють з документами, що використовують ці дві функції, і якщо RegisterDoc повертає false (а це станеться тільки в тому випадку, якщо номер уже є в списку), то користувачеві видається повідомлення, що з документом вже працюють. Функція UnregisterDoc просто видаляє номер зі списку.

    На клієнті знадобиться, окрім доступу до двох провайдерам, ще пара функцій - отримання нового значення для CLIENT_ID довідника клієнтів і отримання повного імені клієнта. Для цього необхідно створити опис цих функцій в бібліотеці типів.

    Залежно від того, який синтаксис використовується в редакторі бібліотеки типів (IDL або Pascal), оголошення цих функцій виглядає по-різному, нижче наведено їх опису в protected-секції модуля даних:        

    protected   

    class procedure UpdateRegistry (Register:   Boolean; const ClassID, ProgID: string);   

    override;   

    function NewClientID: Integer; safecall;   

    function Get_ClientName (ClientID: Integer):   WideString; safecall;     

    На IDL це виглядає так:        

    [id (0x00000001)]   

    HRESULT   _stdcall NewClientID ([out, retval] long * Result);      

    [propget,   id (0x00000004)]   

    HRESULT   _stdcall ClientName ([in] long ClientID, [out, retval] BSTR * Value);     

    Реалізація цих функцій досить проста. Треба викликати процедури, що зберігаються, і видати повертається ними значення в якості результату:        

    function TrdmCommon.NewClientID: Integer;   

    begin   

    lock;   

    with spNewID do   

    try   

    ExecProc;   

    Result: =   paramByName ( 'ID'). AsInteger;   

    finally   

    unlock;   

    end;   

    end;      

    function TrdmCommon.Get_ClientName (ClientID: Integer): WideString;   

    begin   

    lock;   

    try   

    with spClientFullName do   

    begin   

    paramByName ( 'ID'). AsInteger   : = ClientID;   

    ExecProc;   

    Result: =   paramByName ( 'FULL_NAME'). AsString;   

    end;   

    finally   

    unlock;   

    end;   

    end;     

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

    Малюнок 3.

    Тут вже все трохи складніше. Зрозуміло, тут теж є з'єднання з базою ibdDoc, налаштована на сервер БД. Збережені процедури spNewID видає на цей раз номер для нового документа, використовуючи процедуру DOC_TITLE_ID, аналогічну процедуру CLIENT_ID.

    На цей раз в модулі даних, крім компонентів запитів до сервера, присутні два компоненти TСlientDataSet і два провайдера даних. Ці додаткові компоненти призначені саме для організації розрахунків на сервері. Оскільки, як ми домовилися, на сервері додатків повинна розраховуватися сума документа, то на ньому має бути відомо вміст документа до того, як воно буде збережено в БД. Зрозуміло, це можна здійснити, використовуючи події провайдера для попередньої обробки пакету змін, що надійшов від клієнтської частини, але мені хотілося показати можливість організації роботи з документом як з повноцінним об'єктом.

    Ідея проста: нехай весь видалений модуль даних працює з одним документом. У такому випадку цей модуль буде виглядати для клієнтської частини як повноцінний об'єкт, що володіє всіма даними документа і надає клієнтської частини всі необхідні властивості. Зрозуміло, від пакетів даних ніхто не відмовляється.

    Таким чином, організовується такий алгоритм роботи: Клієнтська частина створює на сервері або новий документ, або відкриває існуючий (видалення документів вже реалізовано). Сервер додатків створює модуль даних і, якщо необхідно, закачує в нього вміст документа з сервера БД. Після цього клієнтська частина і видалений модуль даних спільно обробляють ці дані, займаючись кожен своєю справою: клієнтська частина надає засоби для зміни цих даних користувачем, а віддалений модуль робить всі необхідні розрахунки.

    До одного компоненту транзакції ibtDoc приєднано на Цього разу два запити ibqTitle і ibqBody, відповідно вибирають один рядок заголовка документа (select * from DOC_TITLE where DOC_ID =: DocID) і все рядки цього документа (select * from DOC_BODY where DOC_ID =: DocID).        

    ПРИМІТКА   

    Хоча MIDAS вимагає наявності своєї   IBTransaction для кожної пари компонентів "IBQuery-провайдер", в   даному випадку це не є обов'язковим. Провайдери не будуть починати і завершувати   транзакції, відкриватися і закриватися транзакція буде явно, у   відповідних методах.     

    До цих запитах приєднані провайдери dspTitleInner і dspBodyInner, призначення яких - отримати дані з сервера БД і передати їх у відповідні ClientDataSet. Властивість Exported у цих провайдерів встановлено в false, вони потрібні тільки всередині сервера додатків, і бачити їх на клієнтської частини нема чого. Відповідно, клієнтський набір даних cdsTitle (компонент TClientDataSet) отримує один рядок заголовка з dspTitle і cdsBody, вміст документа з dspBody.

    Для того, щоб клієнтська частина могла одержувати і змінювати дані документа, до клієнтських розділами даних cdsTitle і cdsBody приєднані провайдери даних, dspTitle і dspBody, відповідно. Властивості Exported цих провайдерів залишено значення за замовчуванням, True, зате властивість ResolveToDataSet встановлено в True, для того, щоб ці провайдери не намагалися працювати з ClientDataSet за допомогою запитів. Таким чином, клієнтська частина може отримувати та змінювати дані не з TIBQuery, але з TClientDataSet, причому абсолютно про це не здогадуючись. За командою з клієнтської частини зміни, передаються серверу додатків, який і зберігає їх у БД.

    Тепер подивимося, що нам потрібно для подібної реалізації. Функції синхронізації обробки документів RegisterDoc і UnregisterDoc вже є, треба їх тільки використовувати. З їхньою допомогою гарантується, що одночасно один і той же документ редагуватися НЕ буде, тому у провайдерів даних dspTitleInner і dspTitleBody достатньо встановити UpdateMode = upWhereKeyOnly, і визначити ключові поля у запитів. Вміст документа може складатися з декількох рядків, тому у dspBodyInner і dspBody потрібно встановити прапор poAllowMultiRecordUpdates. Тепер потрібно розібратися з полями клієнтських наборів даних, встановивши в них відповідні властивості. Я тут зупинюся лише на властивості ProviderFlags. Оскільки поле «Посилання на документ» (DOC_ID) на клієнтської частини не потрібно, йому можна задати прапор pfHidden. Зрозуміло, у всіх ключових полів (DOC_ID і LINE_NUM) і в наборі даних заголовка, і у вмісті документа треба вказати прапор pfInKey. У провайдерів dspTitle і dspBody потрібно встановити політику оновлення UpdateMode = UpWhereKeyOnly, клієнтська частина у модуля даних одна, і інші значення абсолютно ні до чого.

    Тепер компоненти для зберігання і обробки даних підготовлені, залишилося написати самі методи роботи з ними.

    Давайте розберемося, що саме потрібно. Модуль rdmDoc призначений як для створення нового документу, так і для редагування існуючого. Цей модуль можен знаходитися в одному з трьох станів, описаних в перерахуванні TObjState:

    osInactive: даних немає, документ не редагується,

    osInsert: створений новий документ і

    osUpdate - відбувається зміна існуючого документа.

    Стан зберігається у змінній Fstate, що знаходиться всередині модуля. Відразу після створення і після закінчення обробки документа модуль даних повинен знаходитися в неактивному стані.

    Перехід з одного стану в інший повинен забезпечуватися відповідними методами. Я назвав ці методи DoInactiveState (перевод в неактивний стан), DoOpen (відкрити існуючий документ) і DoCreateNew (створення нового документа). Під час редагування чи додавання документа потрібно знати його унікальний номер, записується в полі DOC_ID. Для цього достатньо оголосити в секції private змінну FDocID: integer, яка і буде його зберігати.

    У бібліотеці типів потрібно реалізувати методи, які будуть створювати документ або відкривати існуючий, а також зберігати зміни. Крім цього, знадобиться властивість, що дозволяє отримати в будь-якій момент суму по документу. Сума кожного рядка вмісту нехай розраховується на клієнтської частини.

    Отже, приступимо. Спочатку описуються методи переходу між станами, вони призначені для внутрішнього використання, і тому їх оголошення містяться в секції private:        

      procedure DoInactiveState;   

    procedure DoCreateNew;   

    procedure DoOpen (DocID: integer);     

    Розглянемо їх по порядку.        

    procedure   TrdmDoc.DoInactiveState;   

    begin   

    UnregisterDoc (FDocID);   

    FDocID: = 0;   

    cdsTitle.Active: = False;   

    cdsBody.Active: = False;   

    ibtDoc.Active: = False;   

    FState: = osInactive;   

    end;     

    Процедура DoInactiveState видаляє документ зі списку редагованих, закриває усі клієнтські набори даних, а також виробляє відкат транзакції (якщо вона була активна).        

    procedure TrdmDoc.DoOpen (DocID: Integer);   

    begin   

    if DocID = 0 then Exit;   

    try   

    if not RegisterDoc (DocID) then   

    raise Exception.Create ( 'Документ редагується');   

    FDocID: = DocID;// і тільки тут, інакше DoInactiveState видалить документ   

    ibdDocs.Connected: = True;   

    ibtDoc.StartTransaction;   

    with cdsTitle do   

    begin   

      params.paramByName ( 'DocID'). AsInteger: = FDocID;   

    Active: = True;   

    if BOF and EOF then   

    raise Exception.Create ( 'Документ не знайдений');   

    end;   

    with cdsBody do   

    begin   

      params.paramByName ( 'DocID'). AsInteger: = FDocID;   

    Active: = True;   

    end;   

    FState: = osUpdate;   

    ibtDoc.Commit;   

    except   

    DoInactiveState;   

    raise;   

    end;   

    end;     

    DoOpen призначена для відкриття існуючого документа, ідентифікатор DOC_ID якого дорівнює вхідному параметру DocID. Першим справою за допомогою RegisterDoc проводиться перевірка того, що документ у даний момент не редагується. Потім ідентифікатор документа запам'ятовується, і в клієнтські набори даних завантажуються дані документа. У разі помилки стан документа переводиться в osInactive.        

    procedure TrdmDoc.DoCreateNew;   

    var   

    NewDocID: Integer;   

    begin   

    try   

    NewDocID: = NewID;   

    if not RegisterDoc (NewDocID)   then   

    raise Exception.Create ( 'Документ редагується');   

    FDocID: = NewDocID;   

    ibdDocs.Connected: = True;   

    ibtDoc.StartTransaction;   

    with cdsTitle do   

    begin   

      params.paramByName ( 'DocID'). AsInteger: = FDocID;   

    Active: = True;   

    Append;   

    Post;   

    end;   

    with cdsBody do   

    begin   

      params.paramByName ( 'DocID'). AsInteger: = FDocID;   

    Active: = True;   

    end;   

    ibtDoc.Commit;   

    FState: = osInsert;   

    except   

    DoInactiveState;   

    raise;   

    end;   

    end;     

    Процедура DoCreateNew призначена для створення нового документа. Вона практично аналогічна попередньої, за винятком того, що ідентифікатор документа виходить від сервера БД за допомогою процедури NewID, яка звертається до збереженої процедури на сервері.

         

     
         
    Реферат Банк
     
    Рефераты
     
    Бесплатные рефераты
     

     

     

     

     

     

     

     
     
     
      Все права защищены. Reff.net.ua - українські реферати ! DMCA.com Protection Status