Совершенствуем перебор записей в Delphi 10.2 Tokyo с классом-помощником TDataSetHelper
Работая с данными в Delphi с помощью классов унаследованных от TDataSet нам часто нужно делать перебор записей. При этом почти всегда сначала требуется отключить элементы управления, сохранить закладку, выключить фильтры, отписаться от событий, а после перебора восстановить всё в обратном порядке. И благодаря возможности в последних версиях Delphi создавать классы-помощники и использовать анонимные функции, вся эта рутина сокращается до нескольких строк кода. Давайте рассмотрим, мой класс-помощник TDataSetHelper и научимся его использовать.
Вначале я сразу оговорюсь, что описанный здесь класс TDataSetHelper будет работать только начиная с Delphi XE3, т.к. только в этой версии появилась возможность создавать классы-помощники и использовать анонимные функции.
Рутина перебора записей
Здесь я покажу, насколько много строк кода приходится писать каждый раз, когда мы просто хотим перебрать все записи в наборе данных в Delphi.
Давайте создадим проект для экспериментов. Итак, создаём оконное приложение для Windows (пункт меню File -> New -> VCL Forms Application), затем на форму кладём таблицу TDBGrid и одну кнопку TButton. Кнопка будет запускать наш первый эксперимент. Зададим кнопке текст «Эксперимент 1». Также кладём на форму компоненты TDataSource и TClientDataSet. Теперь связываем таблицу TDBGrid с источником данных TDataSource (у нас это DataSource1), а источник данных TDataSource связываем с набором данных TClientDataSet (у нас это ClientDataSet1).
Теперь по событию формы OnCreate создаём поля в наборе данных TClientDataSet и заполняем его случайными значениями.
Вот как будет выглядеть форма после запуска нашего приложения:
Теперь напишем код для проведения первого эксперимента. Допустим нам нужно пройтись по всем записям и подсчитать у скольки записей значение поля NumberField больше 100. Делать мы это будем по нажатию на кнопку «Эксперимент 1». После подсчёта результат будет отображать функцией ShowMessage. Вот как будет выглядеть код:
Если вы теперь запустите приложение и нажмёте на кнопку «Эксперимент 1», то увидите, что перебор идёт медленно сверху вниз. При этом таблица всё время перерисовывается. Зачем нам перерисовывать таблицу? Этого нам не нужно, поэтому на время перебора записей нам нужно отключить отображение данных в таблице, а затем включить обратно. Это делается с помощью функций DisableControls и EnableControls.
Помимо этого, обратите внимание, что после перебора текущей записью всё время становится последняя запись. Это нам не подходит. После перебора текущей записью должна стать та, которая была текущей до перебора. Восстановить текущую запись можно с помощью закладки, свойства Bookmark. Сначала нам нужно сохранить закладку, затем сделать перебор, и после этого восстановить текущую запись, используя сохранённую закладку.
После изменений код подрос и стал таким:
И так приходится делать постоянно, когда вы работаете с данными в Delphi. Поверьте, это очень скучно и утомительно каждый раз писать один и тот же код. Но и это ещё не всё.
Теперь предположим, что пользователь из всего множества записей видит в таблице только сегодняшние записи. Т.е. у которых в поле DateField стоит текущая дата. Чтобы это сделать просто включим фильтр по полю DateField. Для этого после того как мы по событию OnCreate заполнили набор данных напишем следующие две строчки:
После этого при запуске программы мы увидим намного меньше записей. И если мы проведём наш эксперимент, то перебор записей будет выполнен только для тех записей, которые соответствуют условию фильтра. Но нам нужен перебор по всему множеству записей. Поэтому перед началом перебора отключаем фильтр, а затем включаем снова. В итоге код ещё увеличится:
А ещё вспомним о том, что бывают ситуации, когда мы подписаны на события AfterScroll и BeforeScroll, чтобы отслеживать изменение текущей записи. Но ведь для перебора нам это не нужно. Значит, на время перебора нам нужно временно отключить эти события.
Заодно ещё сразу подумаем, что набор данных может быть неактивным. В этом случае при попытке перебора мы получим ошибку, что не хотелось бы.
В итоге получим следующий код перебора записей:
Использование функции ForEach класса-помощника TDataSetHelper для перебора записей
Чтобы каждый раз при переборе записей не делать ту рутину, которую вы видите выше, я сделал класс-помощник TDataSetHelper и опубликовал его на GitHub-е здесь. Для уменьшения кода нам поможет функция ForEach. Чтобы воспользоваться функцией, давайте добавим ещё одну кнопку и назовём её «Эксперимент 2». Затем подключим юнит DataSetHelper. По событию от второй кнопки сделаем тот же подсчёт, что и по событию от первой кнопки, но с помощью функции ForEach. Смотрите, как уменьшится код:
Этот код намного меньше и понятнее. Давайте посмотрим, что делает функция ForEach:
- Проверяет, активен ли набор данных и, если активен, то делает перебор;
- Отключает события AfterScroll и BeforeScroll;
- Отключает перерисовку элементов управления, если она включена;
- Сохраняет закладку для текущей записи;
- Отключает фильтр, если он включён;
- Проходит по всем записям и для каждой вызывает анонимную функцию;
- Включает фильтр обратно, если он был включён;
- Включает перерисовку элементов управления, если она была включена;
- Восстанавливает события AfterScroll и BeforeScroll.
Здесь приведён самый частый случай использования функции. Но что делать, если вам, например, не нужно отключать фильтр или не восстанавливать текущую запись? А может быть вам нужно сделать перебор в обратном порядке или начать с текущей записи? Для настройки перебора предусмотрены специальные флажки:
-
-
- dsloNoRestoreBookmark - запрещает восстановление закладок.
- dsloNoDisableControls - запрещает отключение элементов управления.
- dsloNoDisableFilter - запрещает отключение фильтров.
- dsloNoNullifyBeforeScrollEvent - запрещает обнуление события BeforeScroll.
- dsloNoNullifyAfterScrollEvent - запрещает обнуление события AfterScroll.
- dsloFromCurrent - начинать с текущей записи.
- dsloReverseOrder - идти в обратном порядке.
- dsloOnlyCurrentRecord - только текущая запись.
- dsloOnlyModifiedRecords - только измененные записи.
-
Как пользоваться флажками я покажу на примере. Допустим, нам нужно пройтись по записям в обратном порядке не отключая фильтр. Выглядеть это будет так:
Как видите, первым параметром идёт набор флажков, а вторым параметром – анонимная функция.
Теперь рассмотрим ситуацию, когда нам нужно выйти из цикла. Например, нам нужно подсчитать сумму чисел только в первых 10-ти записях. Для такого случая используется экземпляр класса TDataSetLoopState, с помощью которого мы можем сообщить функции ForEach, что дальнейший перебор записей не нужен. Делается это так:
Здесь в нашу анонимную функцию всё время передаётся указатель на экземпляр класса TDataSetLoopState, у которого есть функция Break для прерывания цикла. Кстати, у функции Break есть параметр restoreBookmark, с помощью которого вы можете запретить восстановление текущей записи. Это может быть полезно, например, когда вы выполняете поиск по записям и если не нашли то что искали, то закладка восстанавливается, а если нашли, то текущей делается запись с найденным результатом. Вот пример поиска определённого числа:
Как видите, здесь если мы нашли число 100, то закладку не восстанавливаем и получится, что мы передвинем индикатор в таблице к строке с найденным числом:
Запись данных в XML с помощью класса-помощника TDataSetHelper
Кроме собственно самого перебора можно выделить и ещё несколько частых действий с данными. Одно из них – это запись данных в XML-документ, например, с целью дальнейшей передачи этого XML в процедуру базы данных или для сохранения в файл. Для этих целей у класса-помощника TDataSetHelper есть функции ToXML. Вот пример кода для создания XML-документа по набору данных и записи его в файл:
А вот фрагмент получившегося файла test.xml:
Как видите, для каждой записи создаётся элемент, а для каждого поля – атрибут. Имя атрибута соответствует имени поля.
По формату записи можно отметить следующее, что дата записывается в строку согласно формата ISO8601, а в числах десятичные дроби отделены точкой.
Теперь давайте посмотрим, что делать, если не все поля из набора данных нужно передать, а только некоторые из них. Для этого нужно в функцию ToXML передать массив строк с именами полей:
Результат будет следующим:
Также вы можете менять имена элементов, например, если нам нужен корневой элемент с именем «Collection», а для каждой записи элементы с именем «Item», то в функцию ToXML нужно указать путь к элементу для записи следующим образом:
Результат будет следующим:
Также функция ToXML умеет принимать флажки для настройки перебора и добавлять данные в существующий xml-документ. Подробности, смотрите в исходниках.
Проверка наличия записей в TDataSet
При работе с TDataSet частенько приходится узнавать, активен ли набор данных и есть ли в нём записи. Обычно этот код выглядит так:
С классом-помощником TDataSetHelper он сокращается до следующего:
Подсчёт суммы, среднего арифметического, поиск максимума и минимума
Кроме всего перечисленного не лишним будут и функции подсчёта суммы и среднего арифметического для какого-либо числового поля. Или поиск минимума и максимума. Для таких целей я сделал класс-помощник TFieldHelper с функциями Sum, Avg, Min и Max. Вот пример использования:
Функции возвращают тип variant, причём, если записей нет или все поля не имеют значений (т.е. содержат null), то функции вернут null. При переборе записей поля со значениями null пропускаются.
Итог
Вот собственно и всё, что я хотел написать про рутину перебора набора данных и про класс-помощник TDataSetHelper. Если у вас есть вопросы и замечания, оставляйте их в комментариях ниже.
Комментарии
Select count(*) from Table where num > 100
ну прикольная возможность, эти хелперы, нов данном случае это просто прикольная фишка и все
Компоненты Table я уже лет 18 не использую, и без них нормально обхожусь.
Быстрее чем прямые SQL запросы не сделать
В статье нет ни слова про компоненты Table.
ну тут те же яйца, только сбоку описаны
и компоненты типа TBGrid с его закладками, фильтрами, это анахронизм со времен DBF файлов
методика работы та же что и с TTable (и не важно что это у вас в статье абстактный Data Set)
это на первый взгляд, кажется что быстрее и удобнее использовать компоненты типа TDBTable на самом деле это куча проблем всегда будет, и жуткие тормоза, ну а нормальную многопользовательскую работу, вообще не организовать
А методы Append и Post это к запросу TQuery применяете?
SQL запросы, только их.
Цитата: решение такое
Select Count(*) FROM TABLE WHERE NumberField > 100
---
я понимаю, что это вы для примера такую задачу сделали, ну и я ее взял же, чтобы показать как эффективнее будет
для отображения данных, опять же SELECT и сохраняем данные самостоятельно, без всех этих TDBTable и прочегоф
такие компоненты только кажется, что они упрощают, на практике - это тормоза и геморой
Но в статье речь не об SQL-запросах, а о переборе записей на клиенте. Таких задач очень много и TDataSetHelper как раз в этом помогает.
RSS лента комментариев этой записи