Табличная часть управляемой формы в серверном вызове: что выбрать под задачу
Содержание
Табличную часть управляемой формы передают на сервер одним из трёх способов. Если процедура объявлена с директивой &НаСервере, платформа сама отправит на сервер данные формы вместе с Объект.Товары, и там их можно выгрузить в таблицу значений. Если нужен лёгкий вызов &НаСервереБезКонтекста, соберите нужные колонки в массив структур и передайте его параметром. Для тысяч строк или для обмена между двумя формами таблицу кладут во временное хранилище и передают только адрес.
Выбор зависит от объёма и от того, нужно ли серверу менять форму. Контекстный вызов проще всего писать, но он тяжелее остальных. Вызов без контекста быстрее, зато данные придётся упаковать вручную. Хранилище выигрывает на больших объёмах и в сценарии «форма подбора → документ».
Где выполняется код формы и почему таблица «не видна» серверу
В управляемом приложении код модуля формы работает в двух местах: на клиенте и на сервере. Между ними нет общей памяти. Всё, что сервер должен увидеть, клиент отправляет по сети в сериализованном виде.
Директива перед процедурой определяет, что уйдёт на сервер:
| Директива | Где выполняется | Что получает сервер |
|---|---|---|
&НаКлиенте | клиент | ничего, вызова сервера нет |
&НаСервере | сервер | данные формы целиком, включая табличные части Объект |
&НаСервереБезКонтекста | сервер | только переданные параметры |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвали | только параметры |
На клиенте табличная часть документа выглядит как ДанныеФормыКоллекция. Это не ТаблицаЗначений и не табличная часть объекта. У коллекции есть Количество(), Добавить(), НайтиСтроки(), обход циклом — этого хватает для простых правок прямо на клиенте.
Тип ТаблицаЗначений на клиенте не существует. Строка Новый ТаблицаЗначений в клиентской процедуре даст ошибку вида Тип не определен (ТаблицаЗначений). Поэтому всё, что требует запросов, сверток, группировок и соединений, делают на сервере.
Перед тем как писать серверный вызов, проверьте, нельзя ли обойтись клиентом. Пересчитать сумму строки по цене и количеству можно без обращения к серверу. Каждый лишний вызов — это сетевой круг и ожидание для пользователя.
Способ 1. Контекстный вызов &НаСервере
Самый короткий путь. Клиентская процедура вызывает серверную, а платформа сама переносит данные формы туда и обратно. На сервере Объект.Товары доступен как та же коллекция, а метод Выгрузить() превращает её в полноценную таблицу значений.
&НаКлиенте
Процедура СвернутьТовары(Команда)
СвернутьТоварыНаСервере();
КонецПроцедуры
&НаСервере
Процедура СвернутьТоварыНаСервере()
ТЗ = Объект.Товары.Выгрузить();
ТЗ.Свернуть("Номенклатура, Цена", "Количество, Сумма");
Объект.Товары.Загрузить(ТЗ);
КонецПроцедуры
После возврата на клиент форма покажет уже свёрнутую таблицу. Ничего дополнительно передавать не нужно.
Когда нужен объект документа целиком
Если на сервере нужно вызвать экспортную процедуру модуля объекта, преобразуйте реквизит формы в объект и обратно:
&НаСервере
Процедура ЗаполнитьПоОстаткамНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.ЗаполнитьТоварыПоОстаткам(); // экспортная процедура модуля объекта
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
Метод ЗаполнитьТоварыПоОстаткам здесь условный — подставьте процедуру своей конфигурации.
Чем платить за удобство
- 📦 На сервер уходит не одна таблица, а данные формы: все реквизиты и табличные части.
- 🐢 Если в документе 5 000 строк и вызов идёт на каждое изменение поля, пользователь заметит задержку.
- 🔁 Контекстный вызов внутри цикла по строкам — частая причина «тормозящей» формы.
Контекстный вызов оправдан, когда сервер должен изменить данные формы, а таблица небольшая.
Способ 2. Массив структур в вызов без контекста
Директива &НаСервереБезКонтекста не тянет за собой форму. Сервер получает только параметры, поэтому вызов лёгкий. Табличную часть в таком случае упаковывают в массив структур, причём берут только те колонки, которые серверу действительно нужны.
&НаКлиенте
Процедура ПроверитьОстатки(Команда)
Строки = Новый Массив;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Строки.Добавить(Новый Структура("Номенклатура, Количество",
СтрокаТЧ.Номенклатура, СтрокаТЧ.Количество));
КонецЦикла;
Нехватка = НехваткаТоваров(Строки, Объект.Склад);
Для Каждого Описание Из Нехватка Цикл
Сообщить(Описание);
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция НехваткаТоваров(Строки, Склад)
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
Для Каждого Элемент Из Строки Цикл
ЗаполнитьЗначенияСвойств(ТЗ.Добавить(), Элемент);
КонецЦикла;
// дальше: ТЗ во временную таблицу запроса, соединение с остатками по Склад,
// формирование массива строк с описанием нехватки
Результат = Новый Массив;
Возврат Результат;
КонецФункции
ЗаполнитьЗначенияСвойств переносит в строку таблицы значения одноимённых свойств структуры. Колонки таблицы и ключи структуры должны совпадать по именам.
Таблицу значений из массива затем удобно передать в запрос параметром и выбрать из неё во временную таблицу конструкцией ПОМЕСТИТЬ. Для этого колонкам обязательно задают типы, как в примере: без описания типов запрос к таблице значений не выполнится.
Что можно передавать параметром
Через границу клиент–сервер проходят значения, которые платформа умеет сериализовать:
- ✅ примитивы:
Строка,Число,Дата,Булево,Неопределено; - ✅ ссылки на объекты базы;
- ✅
Массив,Структура,Соответствиеи их фиксированные варианты, если внутри тоже допустимые значения; - ✅
УникальныйИдентификатор,ХранилищеЗначения; - ❌
ТаблицаЗначений,ДеревоЗначений, объекты документов и справочников,Запрос, результат запроса — с клиента их не передать, их там просто нет.
Только выделенные строки
Если обработка касается лишь отмеченных пользователем строк, не передавайте всю таблицу. Свойство Элементы.Товары.ВыделенныеСтроки возвращает массив идентификаторов строк. На стороне контекстного вызова строку находят методом Объект.Товары.НайтиПоИдентификатору(Идентификатор).
Для вызова без контекста соберите на клиенте структуры только по выделенным строкам и добавьте в каждую ключ НомерСтроки — по нему результат потом разложите обратно в таблицу.
Способ 3. Временное хранилище для больших таблиц
Временное хранилище — это область на сервере, куда кладут значение и получают строковый адрес. Передаётся по сети только адрес, а сама таблица остаётся на сервере. Туда можно положить и ТаблицаЗначений, которую на клиент передать нельзя.
Классический сценарий — форма подбора:
- Пользователь выбирает товары в форме подбора.
- При нажатии «Перенести» серверная процедура формы подбора выгружает выбранное в таблицу значений и кладёт её в хранилище.
- Адрес передаётся в форму документа через оповещение о выборе.
- Форма документа вызывает сервер с адресом и загружает строки в табличную часть.
// Форма подбора
&НаСервере
Функция АдресПодобранныхТоваров()
ТЗ = Подобрано.Выгрузить(); // Подобрано — реквизит формы с типом ТаблицаЗначений
// ИдентификаторВладельца — параметр формы подбора, документ передаёт
// в него свой УникальныйИдентификатор при открытии подбора
Возврат ПоместитьВоВременноеХранилище(ТЗ, Параметры.ИдентификаторВладельца);
КонецФункции
&НаКлиенте
Процедура Перенести(Команда)
ОповеститьОВыборе(АдресПодобранныхТоваров());
КонецПроцедуры
// Форма документа
&НаКлиенте
Процедура ТоварыОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
ДобавитьИзХранилища(ВыбранноеЗначение);
КонецПроцедуры
&НаСервере
Процедура ДобавитьИзХранилища(Адрес)
ТЗ = ПолучитьИзВременногоХранилища(Адрес);
Для Каждого СтрокаТЗ Из ТЗ Цикл
ЗаполнитьЗначенияСвойств(Объект.Товары.Добавить(), СтрокаТЗ);
КонецЦикла;
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
Обработчик выбора в форме документа зависит от того, как открывалась форма подбора: если её владелец — табличное поле, срабатывает его событие ОбработкаВыбора, если сама форма — событие формы. Подключите обработчик там, где форма-владелец получает выбор.
Срок жизни значения
Второй параметр ПоместитьВоВременноеХранилище — уникальный идентификатор формы. С ним значение живёт, пока открыта эта форма, и удаляется при её закрытии. В примере выше это форма документа, ведь читать адрес будет она. Поэтому документ передаёт свой идентификатор в форму подбора параметром при открытии: форма подбора закроется раньше, чем данные понадобятся.
Не кладите в хранилище данные без привязки к форме, если читать их собираетесь позже, в другом вызове. Адрес может оказаться пустым, и ПолучитьИзВременногоХранилища вернёт Неопределено. Проверяйте результат перед обходом.
Хранилище нужно и для фоновых заданий. Длительная обработка табличной части запускается через ФоновыеЗадания.Выполнить, а результат задание кладёт по заранее полученному адресу. Форма периодически проверяет состояние задания и забирает результат, не блокируя пользователя.
Сравнение способов передачи
| Способ | Когда выбирать | Ограничения |
|---|---|---|
&НаСервере + Выгрузить() | сервер меняет данные формы, строк немного | тянет все данные формы, тяжёл при частых вызовах |
Массив структур в &НаСервереБезКонтекста | нужен расчёт или проверка по нескольким колонкам | упаковку и распаковку пишете сами |
| Временное хранилище | тысячи строк, обмен между формами, фоновые задания | следите за сроком жизни и удаляйте значение |
Как ускорить передачу и сократить число вызовов
Медленная форма почти всегда объясняется числом вызовов сервера, а не скоростью самого кода. Порядок действий такой:
- Включите замер производительности в отладчике и выполните сценарий. В результатах видны строки с вызовами сервера и время каждой.
- Найдите вызовы внутри циклов. Обращение к серверу на каждую строку при 1 000 строк — это 1 000 сетевых кругов.
- Объедините их: соберите все строки в массив и сделайте один вызов.
- Замените
&НаСерверена&НаСервереБезКонтекстатам, где сервер форму не меняет. - Сократите состав данных: передавайте ссылки и числа, а не представления. Строковые описания сервер получит сам из запроса.
Пример неудачного кода и исправления:
// Медленно: вызов сервера на каждую строку
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
СтрокаТЧ.Цена = ЦенаНоменклатуры(СтрокаТЧ.Номенклатура);
КонецЦикла;
// Быстро: один вызов, ответ — соответствие «номенклатура → цена»
Номенклатура = Новый Массив;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Номенклатура.Добавить(СтрокаТЧ.Номенклатура);
КонецЦикла;
Цены = ЦеныНоменклатуры(Номенклатура); // &НаСервереБезКонтекста, один запрос
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
Цена = Цены.Получить(СтрокаТЧ.Номенклатура);
СтрокаТЧ.Цена = ?(Цена = Неопределено, 0, Цена);
КонецЦикла;
Соответствие проходит через границу клиент–сервер, а поиск по ключу на клиенте не требует сети.
Сериализация в строку и сжатие
Иногда таблицу нужно сохранить или переслать как одно значение: в реквизит базы, в файл, во внешнюю систему. На сервере для этого есть:
ЗначениеВСтрокуВнутр()иЗначениеИзСтрокиВнутр()— внутренний формат платформы, подходит для обмена между базами 1С;Новый ХранилищеЗначения(ТЗ, Новый СжатиеДанных(9))— значение со сжатием, удобно для хранения в реквизите типаХранилищеЗначения;- сериализация в XML или JSON через объекты XDTO — для внешних систем.
Для передачи между клиентом и сервером внутри одной формы сериализовать вручную обычно не нужно. Платформа сделает это сама, а лишняя упаковка в строку только увеличит объём.
Ошибки при передаче и как их ловить
Ошибки на этой границе делятся на два вида: тип нельзя передать и данные не дошли.
Перехват исключений на сервере
Серверную обработку табличной части оборачивайте в Попытка … Исключение, если ошибка в одной строке не должна обрывать весь расчёт. Подробности ошибки берите из ИнформацияОбОшибке():
&НаСервереБезКонтекста
Функция ОбработатьСтроки(Строки)
Ошибки = Новый Массив;
Для Каждого Элемент Из Строки Цикл
Попытка
ОбработатьСтроку(Элемент); // ваша серверная процедура
Исключение
Ошибки.Добавить(СтрШаблон("Строка %1: %2",
Формат(Элемент.НомерСтроки, "ЧГ=0"),
КраткоеПредставлениеОшибки(ИнформацияОбОшибке())));
КонецПопытки;
КонецЦикла;
Возврат Ошибки;
КонецФункции
Пользователю показывайте краткое представление ошибки, а подробное, со стеком, пишите в журнал регистрации через ЗаписьЖурналаРегистрации. Если необработанное исключение возникло на сервере, оно вернётся на клиент, и клиентский код после вызова не выполнится.
Не глушите исключения пустым блоком Исключение … КонецПопытки. Строка «молча» не обработается, а пользователь решит, что всё прошло успешно.
Частые вопросы
Можно ли передать на сервер табличную часть формы обработки, а не документа?
ТаблицаЗначений на клиенте тоже выглядит как ДанныеФормыКоллекция. Подходят те же три способа, а на сервере его выгружают в таблицу через РеквизитФормыВЗначение или метод Выгрузить().Как вернуть таблицу с сервера на клиент?
Можно ли менять табличную часть в вызове без контекста?
Объект.Товары сделайте на клиенте или в контекстном вызове.Сохраняется ли документ в базе после изменения табличной части на сервере?
РеквизитФормыВЗначение и вызовите у него Записать().Помогает ли сжатие при передаче между клиентом и сервером?
ХранилищеЗначения со сжатием полезно для хранения таблицы в реквизите базы, а не для обычного серверного вызова.