Асинхронна завантаження даних за допомогою ado в delphi

У деяких випадках нам доводиться робити асинхронну завантаження даних з бази. Адже так користувач може майже відразу побачити перші фрагменти даних у міру їх завантаження, не чекаючи, поки буде завантажено все повністю. Ще користувач отримає можливість скасувати завантаження в будь-який момент, поки вона відбувається. Ну і звичайно, асинхронна завантаження дозволяє зробити, наприклад, многозакладочний призначений для користувача інтерфейс, на кожній закладці якого можна буде одночасно завантажувати різні дані. У цій статті я приведу рішення, яке дозволить зробити асинхронну покрокову процедуру завантаження даних в таблицю в Delphi за допомогою ADO.

створення проекту

Насправді, в ADO вже все готово для асинхронної завантаження даних, потрібно лише виставити певні опції і обробити необхідні події. Але в Delphi все виявилося не настільки просто. Для відображення даних в Delphi використовується таблиця TDBGrid успадкована від TCustomDBGrid (або численні сторонні розробки, теж успадковані від TCustomDBGrid), що посилається на об'єкт TDataSource, який, в свою чергу, посилається на TDataSet. І в разі роботи з ADO ви повинні використовувати обгортки TADOStoredProc, TADOQuery, TADOTable і TADODataSet, які успадковані від об'єкта TDataSet. Якраз ці обгортки і не дають в повну силу користуватися асинхронної завантаженням даних. Щоб зрозуміти, що працює і що не працює при використанні цих обгорток, зробимо простий приклад з їх використанням.

Тепер в Delphi зробимо новий проект з VCL-формою (пункт меню File -> New -> VCL Forms Application - Delphi), на форму покладемо таблицю TDBGrid і дві кнопки TButton. Одна кнопка буде для початку завантаження, а друга для скасування завантаження. Імена кнопок я дам відповідні - ButtonLoad і ButtonCancel. Всі інші імена залишу без змін. Кнопку скасування за замовчуванням зробимо неактивною, Enabled виставимо в False. Задамо, також, заголовок форми і текст на кнопках. Ще я додам компонент TProgressBar, який буде нам показувати чи йде процес завантаження чи ні і компонент TLabel для відображення кількості завантажених записів. Властивість Style прогрес-бару виставимо в pbstMarquee, а Visible - в False.

Після того як з візуальною частиною форми закінчено, покладемо на форму компоненти TDataSource, TADOQuery і TADOConnection. Тепер потрібно зв'язати таблицю TDBGrid (у нас вона отримала ім'я DBGrid1) з джерелом даних TDataSource (у нас він з ім'ям DataSource1), джерело даних - із запитом TADOQuery (у нас він з ім'ям ADOQuery1), а запит - з з'єднанням TADOConnection (у нас воно з ім'ям ADOConnection1). Тобто виходить ось такий зв'язок: DBGrid1 -> DataSource1 -> ADOQuery1 -> ADOConnection1. Для цього встановлюємо у таблиці властивість DataSource рівним DataSource1, властивість джерела даних DataSet рівним ADOQuery1, а властивість запиту Connection рівним ADOConnection1.

Асинхронна завантаження даних за допомогою ado в delphi

Тепер потрібно задати властивості з'єднання у властивості ConnectionString об'єкта ADOConnection1. У мене це буде наступний рядок:

Тут я відразу вказав ім'я користувача і пароль, тому можна відключити стандартне вікно запиту імені користувача та пароля виставивши властивості LoginPrompt компонента ADOConnection1 значення False.

Тепер придумаємо такий запит, який буде довго виконуватися, і запишемо його в властивість SQL компонента ADOQuery1. Заодно будемо засікати, скільки будуть завантажуватися дані для кожного з варіантів завантаження. Я зробив ось такий запит:

У мене цей запит в SQL Management Stodio виконався за 1 хвилину і 9 секунд.

Простий синхронний запит даних в Delphi за допомогою ADO

Для початку зробимо простий синхронний запит даних. Виконувати запит будемо за подією OnClick кнопки ButtonLoad. Ось такий буде код для виконання запиту:

Асинхронна завантаження даних за допомогою ado в delphi

Простий асинхронний запит даних в Delphi за допомогою ADO

Тепер спробуємо виконати цей запит асинхронно. Для цього встановіть у властивості ExecuteOptions компонента ADOQuery1 прапорець eoAcyncExecute в True. А щоб з'єднання з СУБД теж відбувалося асинхронно, встановіть властивості ConnecOptions значення coAsyncConnect. Кількість записів тепер потрібно буде показувати тільки після того як запит виконається. Про це можна дізнатися за подією AfterOpen компонента ADOQuery1. Помилки можна відловити за подією OnExecuteComplete компонента ADOConnection1. Поки запит обробляється, будемо показувати прогрес-бар, робити активної кнопку «Скасування», а кнопку «Завантаження» - неактивною. Скасовувати виконується запит будемо як годиться в ADO - викликом функції Cancel. Ось як буде виглядати код:

Асинхронна завантаження даних за допомогою ado в delphi

Скасування простого асинхронного запиту даних в Delphi за допомогою ADO

Завантаження з прикладу вище працює прекрасно, поки ви не натиснете на кнопку «Скасування». Тут є 2 проблеми:

    1. Коли дані вже почали завантажуватися на клієнта, скасування не спрацьовує. Замість скасування програма зависає на сходинці «ADOQuery1.Recordset.Cancel;», поки всі дані повністю не завантажаться.
    2. Після виклику функції «ADOQuery1.Recordset.Cancel;» (якщо звичайно запит реально скасували, см. П. 1), повторно виконати запит не вийде, тому що виклик методу «ADOQuery1.Open;" не зробить ніяких дій. Це відбувається через те, що компонент ADOQuery1 залишається закритим (Active = False), але в той же час його статус показує, що запит відкривається (State = dsOpening). Це наочно показує на те, що обгортки VCL над ADO не підтримують скасування.

Першу проблему можна спробувати обійти одним з трьох способів:

    1. Поміняти драйвер, якщо інший драйвер у вас є. Але, може статися так, що потрібного вам драйвера не існує або БД не підтримує скасування запитів.
    2. Використовувати серверний курсор (для цього потрібно виставити у компонента ADOQuery1 властивість CursorLocation в clUseServer). Але з серверним курсором ви не зможете дізнатися кількість записів і для відображення даних потрібно постійне підключення до СУБД. Тому, щоб працювати з даними на клієнті, вам доведеться скопіювати їх в оперативну пам'ять (наприклад, в компонент TClientDataSet) або в локальну базу даних, якщо обсяг даних дуже великий. А читання всіх даних з серверного курсора відбувається повільно.
    3. Зробити фіктивну зупинку завантаження. Тобто показувати користувачеві, що запит скасований, а насправді давати запитом виконатися до кінця і тільки після цього його видаляти. Можна, звичайно видаляти компоненти TADOQuery під час завантаження (це можливо, якщо виставити прапор eoAsyncFetchNonBlocking в True), але завантаження даних об'єктами ADO при цьому не зупиниться, вона буде продовжуватися. Це видно навіть в диспетчері завдань. До того ж при видаленні компонентів TADOQuery під час завантаження періодично відбувається помилка «access violation at 0x1cbaf811: read of address 0x00000000» десь в надрах ADO.

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

З огляду на виниклі зі скасуванням проблеми, код форми сильно зміниться. Я приберу з форми компоненти ADOQuery1 і ADOConnection1 і буду створювати їх при кожному завантаженні даних. Нижче я приведу два приклади і опишу їх мінуси.

Ось перший приклад. Тут я використовую серверний курсор для отримання даних. Для відображення я копіюю дані в компонент TClientDataSet. Скасування завантаження тут працює чудово. Але є інші проблеми, про які буде написано нижче.

Асинхронна завантаження даних за допомогою ado в delphi

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

Асинхронна покрокова завантаження даних в Delphi за допомогою ADO

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

Є два варіанти, як це можна зробити. Перший варіант - це невелика зміна попереднього прикладу, де завантаження відбувається з серверного курсора. Сенс зміни полягає в тому, що ми повинні відразу прив'язати таблицю до компоненту TClientDataSet і в міру наповнення компонента TClientDataSet, таблиця відразу буде відображати нові записи. Тут поміняються тільки методи AfterOpen і WndMethod:

Другий варіант - це установка прапора eoAsyncFetchNonBlocking в True у компонента TADOQuery і поступова завантаження даних в подію OnFetchProgress. Цей спосіб працює в 10 разів швидше, ніж попередній. Правда, тут я зіткнувся з однією проблемою: при вибірці даних по події OnFetchProgress неможливо встановити курсор на перший запис (виклики методів First або Prior не допомагають) і, в результаті, у нас відбувається завантаження всіх записів, крім першої. Щоб це уникнути, будемо завантажувати дані безпосередньо за допомогою ADO. Приклад зміниться сильно, тому я наведу тут повний текст:

Асинхронна покрокова завантаження декількох наборів даних в Delphi за допомогою ADO

Якщо вам потрібно асинхронно завантажити кілька наборів даних, так само як це зроблено в попередньому прикладі, то ви можете навіть не намагатися це робити. Справа в тому, що коли ви будете переходити до наступного набору даних за допомогою «query.Recordset: = query.NextRecordset (recordsAffected);», в цей момент компонент TADOQuery закриває курсор і більше ви нічого не отримаєте. Так, це прекрасно працює при синхронних запитах, але тільки не за подією OnFetchProgress. Приклад з читанням з серверного курсора теж працювати не буде.

Схожі статті