Работа с коллекциями данных, такими как ТаблицаЗначений, является фундаментом разработки на платформе 1С:Предприятие. Однако начинающие программисты часто сталкиваются с ситуацией, когда данные, помещенные в таблицу (например, ссылки на справочники или документы), при попытке их извлечения в цикле ведут себя странно: методы объекта не вызываются, а система сообщает, что значение является строкой. Разберем по шагам, почему это происходит, в чем заключается логическая ошибка при написании цикла и как правильно типизировать колонки для стабильной работы кода.
Рассмотрим ситуацию, с которой столкнулся автор темы. В его коде создается ТаблицаЗначений, в нее добавляются колонки и заполняются данные из выборки запроса. Проблема проявляется на этапе чтения данных. Проанализируем фрагмент кода, содержащий ошибку:
// Создание и заполнение таблицы
ТаблицаДействий = Новый ТаблицаЗначений;
ТаблицаДействий.Колонки.Добавить("Наименование");
ТаблицаДействий.Колонки.Добавить("ИсточникДляЗаполнения");
Пока Выборка.Следующий() Цикл
СтрокаТаблицаДействий = ТаблицаДействий.Добавить();
СтрокаТаблицаДействий.Наименование = Выборка.Ссылка; // Здесь записывается Ссылка
СтрокаТаблицаДействий.ИсточникДляЗаполнения = Выборка.ИсточникДляЗаполнения;
КонецЦикла;
// Ошибочный цикл
Для каждого ТекущаяСтрока из СтрокаТаблицаДействий Цикл
СтрТЧ = Объект.ЗаполнениеРеквизитовДоговора.Добавить();
СтрТЧ.РеквизитДоговора = ТекущаяСтрока.Наименование; // Ошибка!
КонецЦикла;
В чем здесь заключается главная ошибка? Обратите внимание на объект, который обходится циклом Для каждого. Автор использует переменную СтрокаТаблицаДействий. В контексте предыдущего цикла Пока эта переменная хранит последнюю добавленную строку таблицы (объект типа СтрокаТаблицыЗначений), а не всю таблицу целиком.
Когда мы применяем цикл Для каждого к одной конкретной строке, итератор 1С начинает обходить не строки таблицы, а значения в ячейках этой строки. На первой итерации в переменную ТекущаяСтрока попадет значение из первой колонки (ссылка), на второй — из второй (строка "ИсточникДляЗаполнения"). В результате, когда вы пытаетесь обратиться к ТекущаяСтрока.Наименование, система выдает ошибку, так как переменная ТекущаяСтрока в этот момент сама является значением, а не объектом строки с набором свойств.
Для корректного получения данных необходимо обходить саму коллекцию — объект ТаблицаЗначений. В этом случае на каждой итерации переменная цикла будет содержать полноценный объект СтрокаТаблицыЗначений, у которого есть доступ к колонкам по их именам. Посмотрим на исправленный вариант:
// Правильный цикл по всей таблице
Для каждого ТекущаяСтрока Из ТаблицаДействий Цикл
СтрТЧ = Объект.ЗаполнениеРеквизитовДоговора.Добавить();
// Теперь ТекущаяСтрока — это строка таблицы,
// и мы можем обращаться к её колонкам:
СтрТЧ.РеквизитДоговора = ТекущаяСтрока.Наименование;
СтрТЧ.Комментарий = ТекущаяСтрока.ИсточникДляЗаполнения;
КонецЦикла;
В данном случае, если в колонку "Наименование" была записана ссылка (например, СправочникСсылка.Договоры), она останется ссылкой. Важно понимать: 1С никогда не меняет тип данных самостоятельно при обычном переборе. Если вам кажется, что "объект превратился в строку", скорее всего, вы либо обращаетесь не к тому объекту, либо смотрите на представление ссылки в отладчике, которое действительно выглядит как текстовое наименование.
В примере выше колонки добавлялись простым способом: ТаблицаДействий.Колонки.Добавить("Наименование"). При таком объявлении колонка имеет тип Произвольный. Это означает, что в одну и ту же ячейку в разных строках можно записать и число, и ссылку, и строку. Хотя платформа это позволяет, для стабильной работы (особенно при взаимодействии с элементами интерфейса) рекомендуется использовать явную типизацию.
Рассмотрим, как правильно задать тип колонки:
// Определяем массив типов (в данном случае один тип - ссылка на договоры)
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("СправочникСсылка.ДоговорыКонтрагентов"));
ОписаниеТиповСсылки = Новый ОписаниеТипов(МассивТипов);
// Добавляем типизированную колонку
ТаблицаДействий.Колонки.Добавить("Наименование", ОписаниеТиповСсылки, "Договор");
Использование ОписаниеТипов дает несколько преимуществ:
Автор темы упоминал, что планировал передать таблицу на клиент. Здесь кроется еще одна важная особенность платформы 1С 8.3. Объект ТаблицаЗначений является исключительно серверным. Его нельзя передать в процедуру, выполняемую &НаКлиенте.
Проанализируем способы решения этой задачи:
Если данные нужно просто "прочитать" на клиенте, таблицу можно конвертировать в массив структур. Эти типы данных являются универсальными и передаются между контекстами без ограничений.
// На сервере
Функция ПолучитьДанныеДляКлиента() Экспорт
ТЗ = Новый ТаблицаЗначений;
// ... заполнение ТЗ ...
МассивРезультат = Новый Массив;
Для каждого СтрокаТЗ Из ТЗ Цикл
СтруктураСтроки = Новый Структура("Наименование, Источник", СтрокаТЗ.Наименование, СтрокаТЗ.ИсточникДляЗаполнения);
МассивРезультат.Добавить(СтруктураСтроки);
КонецЦикла;
Возврат МассивРезультат;
КонецФункции
Если таблица должна отображаться на форме и редактироваться пользователем, ее нужно создать как Реквизит формы с типом "Таблица значений". На уровне формы этот объект превращается в ДанныеФормыКоллекция, который доступен и на клиенте, и на сервере.
Для преобразования программно созданной серверной ТаблицыЗначений в реквизит формы используются специальные методы:
ЗначениеВДанныеФормы(МояТаблицаЗначений, Объект.ИмяРеквизитаНаФорме) — из серверного объекта в экранную форму.ДанныеФормыВЗначение(Объект.ИмяРеквизитаНаФорме, Тип("ТаблицаЗначений")) — обратно для сложной серверной обработки.Часто ТаблицаЗначений используется как временный буфер перед записью в Табличную Часть (ТЧ) документа. Важно помнить, что реквизиты ТЧ всегда имеют строгую типизацию, заданную в конфигураторе. Выясним причину, по которой может возникнуть ошибка при записи:
Если в ТЧ реквизит "Договор" имеет тип СправочникСсылка.Договоры, а вы в коде по ошибке передаете туда строку (например, просто текст с названием договора), 1С не сможет сопоставить этот текст с реальной ссылкой в базе данных. В результате поле останется пустым или вызовется ошибка "Несоответствие типов".
Рассмотрим правильный алгоритм переноса:
ТаблицеЗначений в колонке лежит именно Ссылка (объект базы), а не Наименование (строка).ЗаполнитьЗначенияСвойств(СтрокаПриемник, СтрокаИсточник) для быстрого копирования данных между строкой ТЗ и строкой ТЧ, если имена колонок совпадают.
Для каждого СтрокаТЗ Из ТаблицаДействий Цикл
НоваяСтрокаТЧ = Объект.ЗаполнениеРеквизитовДоговора.Добавить();
// Копирует все совпадающие по именам колонки автоматически
ЗаполнитьЗначенияСвойств(НоваяСтрокаТЧ, СтрокаТЗ);
КонецЦикла;
Подводя итог, можно сказать: ТаблицаЗначений — это мощный инструмент, требующий внимательности к двум вещам: контексту исполнения (сервер) и правильности обхода коллекции. Ошибка с "превращением в строку" почти всегда связана с неправильным использованием цикла Для каждого или обращением к представлению объекта вместо самого объекта. Всегда проверяйте, что именно вы перебираете, и старайтесь типизировать колонки еще на этапе их создания.