При разработке подбора номенклатуры или доработке списков в обычных формах (например, в 1С:Управление торговлей 10.3) программисты часто сталкиваются с задачей вывода оперативных данных, таких как текущие остатки на складах. Основным инструментом для этого является событие табличного поля ПриПолученииДанных. Однако часто возникает вопрос: насколько этично и производительно выполнять запрос к базе данных каждый раз, когда пользователь прокручивает список? Разберем эту ситуацию подробно, изучим механизмы платформы и способы оптимизации.
Для начала проанализируем, почему это событие вообще существует. В обычных формах 1С есть два похожих события для оформления строк: ПриВыводеСтроки и ПриПолученииДанных. Главная проблема ПриВыводеСтроки заключается в том, что оно вызывается отдельно для каждой отрисовываемой строки. Если на экране отображается 20 позиций номенклатуры, это событие сработает 20 раз. Если внутри него написать запрос, мы получим 20 обращений к серверу БД, что недопустимо с точки зрения производительности.
Событие ПриПолученииДанных работает по принципу пакетной обработки. Оно вызывается один раз для всего набора строк, которые платформа планирует отобразить в данный момент. Параметр события ОформленияСтрок содержит коллекцию всех видимых строк. Это позволяет нам собрать все ссылки на номенклатуру в массив и выполнить один запрос ко всем нужным данным сразу. Это стандартный и методологически верный подход в архитектуре 1С 8.x для обычных форм.
Рассмотрим, как правильно организовать получение данных внутри этого события. Основная задача — минимизировать количество вызовов и объем передаваемых данных. Разберем по шагам типичный алгоритм:
ОформленияСтрок результатами запроса.Посмотрим на пример реализации:
Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
// 1. Собираем ссылки в массив
МассивНоменклатуры = Новый Массив;
СоответствиеСтрок = Новый Соответствие;
Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
МассивНоменклатуры.Добавить(СтрокаОформления.ДанныеСтроки.Ссылка);
СоответствиеСтрок.Вставить(СтрокаОформления.ДанныеСтроки.Ссылка, СтрокаОформления);
КонецЦикла;
// 2. Выполняем пакетный запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивНоменклатуры)) КАК ТоварыНаСкладахОстатки";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
// 3. Выводим данные в строки
Пока Выборка.Следующий() Цикл
ТекущаяСтрока = СоответствиеСтрок.Получить(Выборка.Номенклатура);
Если ТекущаяСтрока <> Неопределено Тогда
ТекущаяСтрока.Ячейки.Остаток.УстановитьТекст(Выборка.Остаток);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Несмотря на то что пакетный запрос — это нормально, при быстром скроллинге списка запросы все равно могут нагружать сервер. Выясним, как этого избежать. Один из самых эффективных способов — использование локального кэша на клиенте.
Мы можем создать переменную модуля формы (например, типа Соответствие), в которой будем хранить уже полученные остатки. Перед тем как добавлять ссылку в массив для запроса, программа будет проверять: "А нет ли у нас уже данных по этой позиции?".
Рассмотрим нюанс с актуальностью данных. Остатки — вещь динамическая. Чтобы кэш не показывал неверную информацию вечно, можно добавить "время жизни" (TTL). Например, хранить данные в кэше не более 2-5 минут. Для этого в кэш нужно записывать не просто число, а структуру с полями Значение и ВремяПолучения.
Проанализируем ситуацию: пользователь быстро крутит колесико мыши, пролистывая сотни строк. Платформа генерирует событие ПриПолученииДанных на каждом шаге. Если мы будем выполнять запрос мгновенно, сервер получит десятки запросов за секунду, данные которых пользователь даже не успеет рассмотреть.
Решение — использование обработчика ожидания. Суть метода:
ПриПолученииДанных мы не выполняем запрос сразу, а лишь сохраняем список ссылок в буферную переменную.ПодключитьОбработчикОжидания на 0.3 секунды.Это радикально снижает нагрузку на систему в моменты активного поиска товара.
Если номенклатурная база огромна, а условия получения остатков сложные (например, нужно учитывать резервы или остатки по конкретным характеристикам), важно правильно составить сам запрос. Выясним основные правила:
Ссылка. Это ускорит соединение с виртуальными таблицами остатков.Номенклатура.Родитель.Наименование внутри запроса в этом событии, так как это вызывает неявные соединения таблиц и замедляет выполнение кода.Иногда стоит пересмотреть методологию работы пользователя. Нужно ли ему видеть остатки по всем 50 позициям, отображаемым на экране? Часто более правильным решением с точки зрения UX (интерфейса) и производительности будет вывод детальных остатков (в разрезе складов, характеристик, ячеек) в отдельное табличное поле в нижней части формы.
В этом случае запрос выполняется только для одной текущей строки, выделенной пользователем. Это происходит в событии ПриАктивизацииСтроки (также с небольшой задержкой через обработчик ожидания, чтобы избежать лагов при перемещении курсора кнопками клавиатуры). Такой подход реализован во многих типовых конфигурациях 1С, так как он дает больше информации (детализация по складам) при меньшей нагрузке на систему.
Если ваша конфигурация позволяет использовать управляемые формы (или режим "управляемые формы в обычном приложении"), стоит рассмотреть переход на ДинамическийСписок. В нем механизм подгрузки данных оптимизирован на уровне платформы. Динамический список сам управляет порциями данных и кэшированием, что избавляет разработчика от необходимости вручную прописывать логику в ПриПолученииДанных. Однако для "чистых" обычных форм на платформе 8.2 или старых версиях 8.3 вышеописанные методы оптимизации остаются наиболее актуальными.
Подведем итог: запрос в событии ПриПолученииДанных — это нормальная практика, если он выполняется пакетно. Для достижения максимальной производительности мы рекомендуем комбинировать пакетную обработку с кэшированием на стороне клиента и использованием индексированных временных таблиц в запросе. Если же интерфейс начинает "подтормаживать", лучшим выходом станет использование обработчика ожидания для реализации "ленивой" подгрузки данных.