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