В процессе разработки прикладных решений на платформе «1С:Предприятие 8» программисты часто сталкиваются с необходимостью получения различных расчетных показателей. Классический сценарий: на форме документа или справочника отображается важный показатель (например, процент брака, общая сумма скидки, текущий статус взаиморасчетов), который рассчитывается «на лету» при открытии. Однако, когда нам требуется получить этот же показатель для множества объектов в цикле (например, при формировании отчета или обработке данных), стандартный подход через получение формы объекта приводит к катастрофическому падению производительности.
В этой статье мы подробно разберем, почему попытка получить форму в цикле — это грубая ошибка, где физически хранятся данные формы и как правильно извлечь нужные показатели быстрыми и оптимальными методами.
Для начала проанализируем структуру данных управляемого приложения. Реквизиты управляемой формы — это элементы её внутреннего контекста. Они создаются в оперативной памяти сервера приложений и передаются на клиент только на время «жизни» этой конкретной формы. Как только пользователь закрывает окно, контекст формы уничтожается.
Платформа физически не записывает значения реквизитов формы в таблицы базы данных (если только эти реквизиты напрямую не связаны с реквизитами самого прикладного объекта, например, через путь к данным Объект.ПроцентБрака). Если значение отображается на форме, но отсутствует в метаданных самого объекта, оно всегда является вычисляемым или загружаемым из внешних источников в момент инициализации этой формы.
Попытка вызвать метод ПолучитьФорму() или открыть форму программно ради одного реквизита заставляет платформу выполнять колоссальный объем лишней работы:
Если выполнять эти действия в цикле для сотен или тысяч документов, сервер баз данных и рабочий процесс «1С» будут перегружены, а выполнение операции затянется на минуты или даже часы.
Прежде чем приступать к написанию кода оптимизации, нам необходимо понять, в какой момент форма получает это значение. Проанализируем жизненный цикл формы при открытии существующего объекта:
ПриЧтенииНаСервере. В этот момент сам прикладной объект уже прочитан, и его данные доступны через параметр ТекущийОбъект.ПриСозданииНаСервере. Здесь происходит основная программная настройка элементов интерфейса и заполнение дополнительных реквизитов формы.Как правило, именно в обработчике ПриЧтенииНаСервере разработчики размещают алгоритмы расчета связанных показателей. Посмотрим на типовой пример того, как это выглядит в модуле формы:
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
// Алгоритм расчета процента брака на основе данных документа
Если ТекущийОбъект.КоличествоВыпуска > 0 Тогда
ЭтотОбъект.ПроцентБрака = (ТекущийОбъект.КоличествоБрака / ТекущийОбъект.КоличествоВыпуска) * 100;
Иначе
ЭтотОбъект.ПроцентБрака = 0;
КонецЕсли;
КонецПроцедуры
Разобравшись с тем, откуда берутся данные, рассмотрим детальные сценарии решения задачи получения этого показателя без обращения к форме.
Самый правильный архитектурный подход — провести рефакторинг кода и вынести логику вычисления показателя из модуля формы. Модуль формы должен отвечать только за отображение, а сами расчеты должны находиться в легкодоступных неконтекстных модулях.
Выполним это по шагам:
ПриЧтенииНаСервере).ОбщиеМеханизмыСервер).Посмотрим на пример реализации такой функции в модуле менеджера документа:
// В модуле менеджера документа "ВыпускПродукции"
Функция РассчитатьПроцентБрака(СсылкаНаДокумент) Экспорт
// Получаем необходимые данные одним быстрым запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВыпускПродукции.КоличествоВыпуска КАК КоличествоВыпуска,
| ВыпускПродукции.КоличествоБрака КАК КоличествоБрака
|ИЗ
| Документ.ВыпускПродукции КАК ВыпускПродукции
|ГДЕ
| ВыпускПродукции.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Если Выборка.КоличествоВыпуска > 0 Тогда
Возврат (Выборка.КоличествоБрака / Выборка.КоличествоВыпуска) * 100;
КонецЕсли;
КонецЕсли;
Возврат 0;
КонецФункции
Теперь в нашей внешней обработке или отчете мы можем легко получить этот процент в цикле, не открывая форму:
Для Каждого СтрокаДокумента Из СписокДокументов Цикл
ПроцентБрака = Документы.ВыпускПродукции.РассчитатьПроцентБрака(СтрокаДокумента.Ссылка);
// Дальнейшая обработка результата
КонецЦикла;
Несмотря на то, что Решение 1 избавляет нас от инициализации формы, вызов функции в цикле все равно порождает проблему N+1 запросов к базе данных. Если в списке 1000 документов, система сделает 1000 отдельных запросов к СУБД, что негативно скажется на производительности.
Проанализируем ситуацию: если алгоритм расчета процента брака можно описать математически, мы можем получить данные для всей выборки документов одним-единственным запросом.
Рассмотрим пример пакетного запроса, который выбирает список документов и сразу рассчитывает процент брака:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВыпускПродукции.Ссылка КАК ДокументСсылка,
| ВЫБОР
| КОГДА ВыпускПродукции.КоличествоВыпуска > 0
| ТОГДА (ВыпускПродукции.КоличествоБрака / ВыпускПродукции.КоличествоВыпуска) * 100
| ИНАЧЕ 0
| КОНЕЦ КАК ПроцентБрака
|ИЗ
| Документ.ВыпускПродукции КАК ВыпускПродукции
|ГДЕ
| ВыпускПродукции.Ссылка В (&МассивСсылок)";
Запрос.УстановитьПараметр("МассивСсылок", МассивДокументов);
РезультатЗапроса = Запрос.Выполнить();
// Выгружаем данные в таблицу значений для быстрой работы в памяти
ТаблицаРезультатов = РезультатЗапроса.Выгрузить();
ТаблицаРезультатов.Индексировать("ДокументСсылка");
Использование этого метода позволяет обработать тысячи документов за доли секунды, полностью исключая обращение к объектной технике и контексту форм.
Довольно часто разработчики путают реквизиты формы с Дополнительными реквизитами, настроенными в режиме «1С:Предприятие» с помощью Библиотеки стандартных подсистем (БСП). Пользователь видит такие реквизиты непосредственно на форме документа, но в конфигураторе в структуре самого документа их действительно нет.
Если это ваш случай, помните: эти данные физически хранятся в базе данных, и получать их нужно специализированными механизмами БСП, либо прямыми запросами.
ДополнительныеРеквизиты самого объекта. Получим их быстрым запросом к табличной части:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДопРеквизиты.Значение КАК ПроцентБрака
|ИЗ
| Документ.ВыпускПродукции.ДополнительныеРеквизиты КАК ДопРеквизиты
|ГДЕ
| ДопРеквизиты.Ссылка = &Ссылка
| И ДопРеквизиты.Свойство.Имя = ""ПроцентБрака""";
ДополнительныеСведения. Для их получения используем запрос к виртуальной таблице среза или к самому регистру:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДопСведения.Значение КАК ПроцентБрака
|ИЗ
| РегистрСведений.ДополнительныеСведения КАК ДопСведения
|ГДЕ
| ДопСведения.Объект = &Ссылка
| И ДопСведения.Свойство.Имя = ""ПроцентБрака""";
Бывают ситуации, когда реквизит формы — это не вычисляемый показатель документа, а персональная настройка пользователя (например, введенное вручную значение, которое сохраняется при закрытии формы и восстанавливается при ее повторном открытии). Такие реквизиты хранятся в системных таблицах базы данных под управлением платформы.
Для того чтобы прочитать такое сохраненное значение без интерактивного открытия формы, мы можем обратиться напрямую к системному хранилищу настроек платформы. Рассмотрим пример чтения сохраненной настройки:
КлючОбъекта = "Документ.ВыпускПродукции.Форма.ФормаДокумента/СохраняемыеЗначения";
КлючНастройки = "ПроцентБракаПоУмолчанию";
ИмяПользователя = Пользователи.ТекущийПользователь().Имя; // Или конкретное имя
ЗначениеНастройки = ХранилищеОбщихНастроек.Получить(КлючОбъекта, КлючНастройки, , ИмяПользователя);
Если ЗначениеНастройки <> Неопределено Тогда
ПроцентБрака = ЗначениеНастройки;
КонецЕсли;
Этот способ позволяет извлекать пользовательские настройки мгновенно, не затрагивая интерфейсные модули и не инициализируя саму форму.
Мы детально разобрали особенности работы с реквизитами управляемых форм. Главный вывод: никогда не получайте форму объекта ради чтения ее данных в цикле. Стремитесь разделять интерфейс и бизнес-логику. Переносите тяжелые расчеты в модули менеджеров, используйте пакетные запросы СУБД или механизмы БСП. Это позволит вашим решениям работать стабильно, быстро и выдерживать высокие нагрузки при масштабировании систем на платформе «1С:Предприятие 8».