При разработке сложных интерфейсов, таких как графики ремонтов, шахматки или табели учета рабочего времени, программисты часто сталкиваются с необходимостью записывать данные в колонки, имена которых определяются во время выполнения кода (например, День01, День02 и так далее). В этой статье мы подробно разберем, как правильно реализовать этот механизм, избежав типичных ошибок начинающих разработчиков.
Рассмотрим ситуацию, описанную автором темы. Имеется документ ГодовойГрафикРемонтов, данные из которого должны транслироваться в МесячныйГрафикРемонтов. В табличной части присутствуют колонки для каждого дня месяца. Основная трудность заключается в том, что при обходе результата запроса разработчик пытается присвоить значение напрямую в выборку запроса:
// ОШИБОЧНЫЙ ПОДХОД
Результат = Запрос.Выполнить().Выбрать();
Пока Результат.Следующий() Цикл
Результат.День08 = "Значение"; // Ошибка: Поле объекта недоступно для записи
КонецЦикла;
Проанализируем причину ошибки: объект ВыборкаИзРезультатаЗапроса предназначен только для чтения данных из базы. Для изменения данных нам необходимо обратиться к самому объекту документа или к реквизиту формы, который содержит данные табличной части.
В языке 1С коллекции (строки табличных частей, структуры, соответствия) позволяют обращаться к своим свойствам не только через точку (Строка.ИмяРеквизита), но и через квадратные скобки, передавая имя реквизита в виде строки (Строка["ИмяРеквизита"]). Это и есть ключ к решению задачи.
Разберем по шагам, как это реализовать в процедуре &НаСервере:
&НаСервере
Процедура ЗаполнитьПоДнямНаСервере(Параметр_Месяц)
// 1. Формируем запрос для получения данных из годового графика
Запрос = Новый Запрос;
// ... (текст запроса из примера автора)
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
// 2. Находим или создаем строку в табличной части текущего документа (Объект)
// Предположим, мы ищем строку по оборудованию
ПараметрыОтбора = Новый Структура("Оборудование", РезультатЗапроса.Оборудование);
НайденныеСтроки = Объект.СписокОборудования.НайтиСтроки(ПараметрыОтбора);
СтрокаТЧ = Неопределено;
Если НайденныеСтроки.Количество() > 0 Тогда
СтрокаТЧ = НайденныеСтроки[0];
Иначе
СтрокаТЧ = Объект.СписокОборудования.Добавить();
ЗаполнитьЗначенияСвойств(СтрокаТЧ, РезультатЗапроса);
КонецЕсли;
// 3. Вычисляем день и формируем имя колонки
// Важно: учитываем ведущий ноль (День01, а не День1)
ДеньЧислом = День(РезультатЗапроса.ДатаПоследнегоРемонта);
ИмяПоляДня = "День" + Формат(ДеньЧислом, "ЧГ=0; ЧВН=");
// 4. ЗАПИСЬ ЗНАЧЕНИЯ
// Используем квадратные скобки для динамического обращения
СтрокаТЧ[ИмяПоляДня] = "ППР"; // Здесь записываем нужное значение
КонецЦикла;
КонецПроцедуры
Как мы заметили в коде выше, критически важно правильно сформировать строковое имя реквизита. Если в метаданных колонка называется День08, а ваш код сформирует строку "День8", система выдаст ошибку "Поле объекта не обнаружено".
Рассмотрим подробнее функцию Формат() для этих целей:
Используйте строку формата "ЧГ=0; ЧВН=":
ЧГ=0 — отключает группировку разрядов (чтобы не появилось пробелов в больших числах).ЧВН= — включает вывод ведущих нулей.Пример формирования:
Для НомерДня = 1 По 31 Цикл
ИмяРеквизита = "День" + Формат(НомерДня, "ЧГ=0; ЧВН=");
// На выходе получим: "День01", "День02" ... "День31"
КонецЦикла;
Если в документе МесячныйГрафикРемонтов сотни или тысячи строк, использование метода НайтиСтроки() внутри цикла по результату запроса может существенно замедлить работу (алгоритм сложности O(n*m)). Проанализируем, как ускорить этот процесс.
Рекомендуется предварительно создать Соответствие, где ключом будет уникальный идентификатор оборудования, а значением — сама строка табличной части:
// Создаем карту соответствия для быстрого доступа
КартаСтрок = Новый Соответствие;
Для Каждого ТекСтрока Из Объект.СписокОборудования Цикл
КартаСтрок.Вставить(ТекСтрока.Оборудование, ТекСтрока);
КонецЦикла;
// Теперь в цикле запроса поиск будет мгновенным
Пока РезультатЗапроса.Следующий() Цикл
СтрокаТЧ = КартаСтрок.Получить(РезультатЗапроса.Оборудование);
Если СтрокаТЧ <> Неопределено Тогда
СтрокаТЧ[ИмяПоляДня] = "Значение";
КонецЕсли;
КонецЦикла;
Проанализируем ситуацию, упомянутую в сообщении №7 форума: "необходимо завести 365 колонок". С точки зрения производительности и гибкости системы 1С, создание огромного количества реквизитов (День01...День31 или тем более 365 дней) в одной таблице считается "антипаттерном".
Почему это плохо:
Как сделать правильно:
Вместо расширения таблицы вширь, используйте подчиненную табличную часть (или регистр сведений) со структурой: Оборудование | Дата | ВидРабот. Для отображения "шахматки" пользователю на форме используйте Таблицу Значений, которая строится динамически при открытии или обновлении формы, но в базе данных хранится в классическом "вертикальном" виде.
Для записи значения в динамический реквизит используйте конструкцию СтрокаКоллекции["ИмяРеквизита"] = Значение. Не забывайте о строгом соответствии имен (включая ведущие нули) и старайтесь использовать кэширование (объект Соответствие) для повышения производительности серверных процедур.