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