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