Разберем типичную ситуацию, с которой сталкиваются разработчики на платформе 1С:Предприятие 8.3, работая с управляемыми формами. Представим, что на форме документа у нас есть несколько табличных частей. Эти табличные части могут заполняться как при первом чтении формы (если документ уже сохранен), так и по интерактивной кнопке, например, "Заполнить". Алгоритм заполнения при этом остается идентичным, а количество и состав строк в таблицах совпадают на 100% независимо от способа вызова процедуры заполнения.
Однако возникает любопытная проблема: когда данные в табличные части загружаются по кнопке "Заполнить", отбор строк, срабатывающий при активации строки в одной из таблиц для фильтрации данных в других, работает мгновенно. Но если те же самые данные, в том же количестве и составе, загружаются в табличные части при вызове процедуры из события ПриЧтенииНаСервере, то тот же самый отбор начинает работать в несколько раз медленнее – порой до 10 раз!
Давайте выясним, почему возникает такое замедление и как мы можем решить эту проблему, обеспечив высокую скорость отбора строк вне зависимости от метода их первоначальной загрузки.
Для того чтобы разобраться в корне проблемы, нам необходимо рассмотреть особенности работы управляемых форм в 1С:Предприятии 8.3 и механизм передачи данных между сервером и клиентом. Ключевую роль здесь играет объект ДанныеФормыКоллекция, к которому относятся все табличные части на форме.
Платформа 1С оптимизирует объем данных, передаваемых с сервера на клиент, особенно для больших коллекций. Вместо того чтобы сразу загружать все данные, она может передавать их порциями – по мере обращения к ним на клиенте. Этот механизм называется "ленивой загрузкой" (lazy loading).
Заполнение в ПриЧтенииНаСервере:
Когда мы заполняем табличные части в процедуре ПриЧтенииНаСервере, этот код выполняется на сервере. Здесь мы получаем доступ к прикладному объекту (например, документу) и можем подготовить данные для формы. После завершения выполнения ПриЧтенииНаСервере прикладной объект может быть удален из памяти сервера, а данные формы начинают существовать на клиенте. Однако, из-за оптимизации платформы, не все строки ДанныеФормыКоллекция могут быть сразу переданы на клиент.
Если после этого на клиенте происходит отбор строк (с помощью метода ОтборСтрок элемента формы), и для выполнения этого отбора требуются данные, которые еще не были переданы на клиент, платформа будет инициировать неявные серверные вызовы. Каждый такой вызов (клиент -> сервер -> клиент) для подгрузки очередной порции данных занимает время. При большом количестве строк и частом отборе эти многократные неявные вызовы на сервер приводят к значительному замедлению работы.
Заполнение по кнопке "Заполнить":
В отличие от ПриЧтенииНаСервере, при заполнении табличных частей по кнопке "Заполнить" (которая, скорее всего, вызывает серверную процедуру из клиентской) контекст работы меняется. В этом сценарии данные, возможно, передаются на клиент более полным образом сразу после выполнения серверной процедуры заполнения. Это связано с тем, что клиент активно ожидает результат операции заполнения, и платформа может обеспечить более эффективную и полную передачу данных в этот момент. В результате, последующие вызовы ОтборСтрок на клиенте работают с уже полностью загруженными данными, что исключает необходимость в неявных серверных вызовах и обеспечивает мгновенную работу.
Таким образом, основная причина замедления кроется в механизме "ленивой загрузки" данных формы с сервера на клиент и множественных неявных серверных вызовах, которые платформа вынуждена делать при попытке отбора по не до конца загруженным данным.
ПриОткрытииОсновываясь на опыте коллег и понимании механизмов работы 1С, эффективным решением этой проблемы является перенос логики заполнения табличных частей из ПриЧтенииНаСервере (или ПриСозданииНаСервере) в клиентское событие ПриОткрытии.
Суть подхода:
Событие ПриОткрытии является клиентским событием, которое срабатывает после того, как форма документа уже была создана и инициализирована, и ее данные уже начали передаваться на клиент. Если мы вызовем серверную процедуру заполнения табличных частей именно из ПриОткрытии, то к моменту применения ОтборСтрок на клиенте все необходимые данные будут уже полностью загружены. Это устраняет проблему неполной загрузки и, как следствие, множественных неявных серверных вызовов.
Пошаговая реализация:
Предположим, у нас есть серверная процедура, которая выполняет заполнение всех наших табличных частей.
// В модуле формы документа
// Это серверная процедура, которая выполняет основную логику заполнения
// всех табличных частей. Она может вызываться как из ПриОткрытии, так и по кнопке.
&НаСервере
Процедура ЗаполнитьВсеТабличныеЧастиНаСервере()
// Предположим, у нас есть несколько табличных частей:
// ТабличнаяЧастьДокумента1, ТабличнаяЧастьДокумента2, ТабличнаяЧастьДокумента3, ТабличнаяЧастьДокумента4
// 1. Очищаем табличные части перед заполнением
// Мы делаем это на сервере, чтобы изменения были применены к данным,
// которые затем будут переданы на клиент.
ТабличнаяЧастьДокумента1.Очистить();
ТабличнаяЧастьДокумента2.Очистить();
ТабличнаяЧастьДокумента3.Очистить();
ТабличнаяЧастьДокумента4.Очистить();
// 2. Пример получения исходных данных для заполнения
// Если документ записан, данные могут быть взяты из него.
// Если новый - из других источников или по умолчанию.
Если Объект.Ссылка.Пустая() Тогда
// Заполнение для нового документа, например, из настроек или временных таблиц
// Запросом получить данные
// ...
Иначе
// Заполнение для существующего документа, возможно, перезаполнение
// запросом к регистру или из других источников, связанных с Объектом
// ...
КонецЕсли;
// 3. Заполняем каждую табличную часть
// Циклы для заполнения строк каждой табличной части.
// Например:
// Для Каждого СтрокаИсточника Из ПолученныеДанныеНаСервере Цикл
// НоваяСтрока1 = ТабличнаяЧастьДокумента1.Добавить();
// НоваяСтрока1.Поле1 = СтрокаИсточника.Значение1;
// НоваяСтрока1.Поле2 = СтрокаИсточника.Значение2;
// НоваяСтрока2 = ТабличнаяЧастьДокумента2.Добавить();
// НоваяСтрока2.ПолеА = СтрокаИсточника.ЗначениеА;
// НоваяСтрока2.ПолеБ = СтрокаИсточника.ЗначениеБ;
// // И так далее для остальных табличных частей
// КонецЦикла;
// В реальной задаче здесь будет сложная логика заполнения,
// включающая запросы к базе данных, обработку данных и т.д.
// Важно, что все эти действия происходят на сервере.
КонецПроцедуры
// Клиентская процедура, которая вызывает серверную при открытии формы
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Вызываем серверную процедуру для заполнения табличных частей.
// Это обеспечит полную передачу данных на клиент после выполнения заполнения.
ЗаполнитьВсеТабличныеЧастиНаСервере();
КонецПроцедуры
// Пример клиентской процедуры, которая вызывает отбор строк
// Это остается без изменений, так как проблема была в загрузке данных, а не в отборе.
&НаКлиенте
Процедура ТабличнаяЧастьДокумента1ПриАктивизацииСтроки(Элемент)
// Проверяем, что есть текущие данные для отбора
Если Элементы.ТабличнаяЧастьДокумента1.ТекущиеДанные <> Неопределено Тогда
// Получаем значение из активной строки для отбора
ПеременнаяДляОтбора = Элементы.ТабличнаяЧастьДокумента1.ТекущиеДанные.ПолеПоКоторомуИдетОтбор;
// Вызываем процедуру отбора для других табличных частей
ВыполнитьОтборВДругихТаблицах(ПеременнаяДляОтбора);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьОтборВДругихТаблицах(ЗначениеОтбора)
// Очищаем существующие отборы
Элементы.ТабличнаяЧастьДокумента2.ОтборСтрок.Очистить();
Элементы.ТабличнаяЧастьДокумента3.ОтборСтрок.Очистить();
Элементы.ТабличнаяЧастьДокумента4.ОтборСтрок.Очистить();
// Добавляем условие отбора для каждой из трех других табличных частей
// Например, отбираем по полю "СвязанноеПоле"
Элементы.ТабличнаяЧастьДокумента2.ОтборСтрок.Добавить("СвязанноеПоле", ЗначениеОтбора);
Элементы.ТабличнаяЧастьДокумента3.ОтборСтрок.Добавить("СвязанноеПоле", ЗначениеОтбора);
Элементы.ТабличнаяЧастьДокумента4.ОтборСтрок.Добавить("СвязанноеПоле", ЗначениеОтбора);
КонецПроцедуры
Важные нюансы:
ПриЧтенииНаСервере на ПриОткрытии.ПриОткрытии на клиенте гарантирует, что форма уже сформирована, и если мы в этот момент активно запрашиваем данные с сервера, платформа с большей вероятностью передаст их на клиент в полном объеме, так как клиентская часть активно ожидает результат.Давайте рассмотрим дополнительные подходы и инструменты, которые помогут нам в работе с производительностью табличных частей:
Операции с большими коллекциями на сервере:
Если табличная часть содержит очень большое количество строк (например, от 20 и более, в зависимости от сложности строк и полей), и нам необходимо выполнять сложные операции обхода или поиска строк (например, метод НайтиСтроки), желательно переносить эти операции на сервер. Выполнение таких действий на сервере позволяет избежать передачи всей коллекции на клиент для каждой операции и минимизировать клиент-серверные взаимодействия.
Например, вместо клиентского:
// Не рекомендуется для больших табличных частей на клиенте
// ЗначениеНайденное = Элементы.МояТаблица.НайтиСтроки(...);
Мы можем реализовать серверный метод:
&НаСервере
Функция НайтиСтрокиВТаблицеНаСервере(ЗначениеОтбора)
МассивНайденныхСтрок = Новый Массив;
Для Каждого СтрокаТЧ Из Объект.МояТаблица Цикл
Если СтрокаТЧ.ПолеДляОтбора = ЗначениеОтбора Тогда
МассивНайденныхСтрок.Добавить(СтрокаТЧ.УникальныйИдентификатор); // Или другое поле для идентификации
КонецЕсли;
КонецЦикла;
Возврат МассивНайденныхСтрок;
КонецФункции
&НаКлиенте
Процедура ПриНеобходимостиНайтиСтроки()
ЗначениеДляПоиска = ...;
НайденныеИдентификаторы = НайтиСтрокиВТаблицеНаСервере(ЗначениеДляПоиска);
// Далее можно работать с полученными идентификаторами на клиенте,
// например, выделить строки или перейти к ним.
КонецПроцедуры
Замер производительности:
В случае возникновения проблем с производительностью, всегда используйте встроенный в 1С "Замер производительности". Этот мощный инструмент, доступный в режиме отладки (F9), позволяет детально проанализировать время выполнения различных участков кода как на клиенте, так и на сервере. Мы можем увидеть точное время, затраченное на каждый метод, функцию, а также определить количество и длительность серверных вызовов. Это поможет точно выявить "узкие места" и подтвердить или опровергнуть гипотезы о причинах замедления.
Проверка виртуальных колонок:
Обратите внимание на возможное наличие виртуальных колонок в табличных частях формы, которые не привязаны напрямую к метаданным или данным объекта. Если такие колонки заполняются в событиях вроде ПриОформленииСтроки или похожих, и их заполнение требует выполнения запросов или сложных вычислений, это может приводить к замедлению на каждой строке. Убедитесь, что логика заполнения таких колонок максимально оптимизирована и не вызывает повторных серверных обращений.
Мы выяснили, что медленная работа отбора строк в табличных частях, заполненных в ПриЧтенииНаСервере, часто связана с особенностями передачи данных между сервером и клиентом в управляемых формах 1С:Предприятия. Платформа может оптимизировать передачу данных, отправляя их порциями, что приводит к неявным серверным вызовам при попытке клиента обратиться к еще не загруженным данным.
Основное решение: перенести вызов процедуры заполнения табличных частей из ПриЧтенииНаСервере в клиентское событие ПриОткрытии. Это способствует более полному кэшированию данных на клиенте сразу после открытия формы, исключая таким образом множественные обращения к серверу при последующем отборе.
Для дальнейшей оптимизации и диагностики всегда используйте "Замер производительности" и стремитесь выполнять ресурсоемкие операции с большими коллекциями данных на сервере. Помните, глубокое понимание клиент-серверного взаимодействия является ключом к разработке высокопроизводительных решений на платформе 1С.