Как отправить строки табличной части формы в серверную процедуру 1С
Содержание
Табличную часть с клиента на сервер в 1С 8.3 передают одним из четырёх способов: через контекстный вызов &НаСервере (вся форма уходит на сервер сама), через параметр процедуры &НаСервереБезКонтекста в виде массива структур, через временное хранилище по адресу или через ссылку на записанный объект. Для управляемых форм в большинстве задач лучше всего работает второй вариант: на клиенте собираете массив структур только с нужными колонками и делаете один серверный вызов.
Таблицу значений на тонком клиенте создать нельзя, а объект документа на клиент не попадает вообще. Поэтому всё, что едет между клиентом и сервером, должно быть сериализуемым: примитивы, ссылки, Массив, Структура, Соответствие и их фиксированные версии. Ниже — как каждый способ устроен, где он ломается и как не тормозить на больших таблицах.
Как устроен обмен данными между клиентом и сервером в 1С 8.3
На клиенте управляемая форма хранит не сам объект, а его копию — данные формы. Реквизит Объект имеет тип ДанныеФормыСтруктура, а его табличная часть Объект.Товары — тип ДанныеФормыКоллекция. Методы объекта (Записать(), Заполнить(), функции модуля объекта) на клиенте недоступны.
Каждый вызов процедуры с директивой &НаСервере или &НаСервереБезКонтекста — это сетевой запрос. Платформа сериализует параметры, отправляет их, выполняет код и везёт результат обратно. Отсюда три правила:
- 📦 Передавать можно только то, что существует и на клиенте, и на сервере.
- 🔁 Каждый вызов стоит времени. Пять вызовов в цикле по строкам — это пять обращений по сети.
- 📏 Чем больше данных в вызове, тем дольше он идёт, особенно через веб-клиент и медленный канал.
Чем отличаются директивы при работе с табличной частью:
| Директива | Что уходит на сервер | Доступ к форме | Когда брать |
|---|---|---|---|
&НаСервере | Вся форма с данными и параметры | Есть: Объект, Элементы, реквизиты | Нужно изменить форму или вызвать метод объекта |
&НаСервереБезКонтекста | Только параметры | Нет | Расчёт, проверка, запрос по переданным строкам |
| Общий модуль с флагом «Вызов сервера» | Только параметры | Нет | Тот же код нужен в нескольких формах |
Способ 1. Контекстный вызов: табличная часть уходит вместе с формой
Самый простой путь. Процедура с директивой &НаСервере видит Объект.Товары напрямую, ничего передавать не нужно. После выполнения изменённые данные формы возвращаются на клиент.
&НаКлиенте
Процедура ПересчитатьЦены(Команда)
ПересчитатьЦеныНаСервере();
КонецПроцедуры
&НаСервере
Процедура ПересчитатьЦеныНаСервере()
Для Каждого Строка Из Объект.Товары Цикл
Строка.Сумма = Строка.Количество * Строка.Цена;
КонецЦикла;
КонецПроцедуры
Если нужен метод модуля объекта, получите сам объект из данных формы и верните его обратно:
&НаСервере
Процедура ЗаполнитьНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.ЗаполнитьТоварыПоОстаткам(); // экспортный метод модуля объекта
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
Минус способа — объём. На сервер уходят все реквизиты формы, включая другие табличные части и динамические списки. Для формы с 2 000 строк товаров и ещё тремя таблицами такой вызов заметно медленнее, чем передача пары колонок.
Контекстный вызов оправдан, когда сервер должен поменять саму форму: переписать строки, обновить реквизиты, заполнить табличную часть заново.
Способ 2. Передача табличной части параметром серверной процедуры
Это основной приём для управляемых форм. На клиенте вы обходите Объект.Товары, складываете нужные поля в структуры, а массив структур отправляете в процедуру &НаСервереБезКонтекста.
&НаКлиенте
Процедура ПроверитьОстатки(Команда)
Строки = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
Строки.Добавить(Новый Структура("НомерСтроки, Номенклатура, Количество",
Строка.НомерСтроки, Строка.Номенклатура, Строка.Количество));
КонецЦикла;
Нехватка = ПроверитьОстаткиНаСервере(Строки, Объект.Склад);
Для Каждого Описание Из Нехватка Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = Описание.Текст;
Сообщение.Поле = "Объект.Товары[" + Формат(Описание.НомерСтроки - 1, "ЧГ=0") + "].Количество";
Сообщение.Сообщить();
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПроверитьОстаткиНаСервере(Знач Строки, Знач Склад)
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число"));
Таблица.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Таблица.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
Для Каждого Данные Из Строки Цикл
ЗаполнитьЗначенияСвойств(Таблица.Добавить(), Данные);
КонецЦикла;
// Здесь — запрос к остаткам с таблицей как параметром
// и сравнение количества. Результат — массив структур.
Результат = Новый Массив;
Возврат Результат;
КонецФункции
Что здесь важно:
- Только нужные колонки. Если для проверки хватает номенклатуры и количества, не тащите цену, сумму, ставку НДС и комментарий.
- Ключевое слово
Знач. Параметры безЗначплатформа после серверного вызова отправляет назад на клиент — на случай, если сервер их изменил. СЗначмассив едет только в одну сторону. - Номер строки в структуре. По нему потом удобно привязать сообщение об ошибке к конкретной ячейке.
- На сервере — таблица значений. Из массива структур её легко собрать через
ЗаполнитьЗначенияСвойстви передать в запрос как параметр.
Как передать только выделенные строки
Если пользователь выделил строки в таблице формы, их идентификаторы лежат в Элементы.Товары.ВыделенныеСтроки. По идентификатору строка находится методом НайтиПоИдентификатору:
&НаКлиенте
Процедура ОбработатьВыделенные(Команда)
Строки = Новый Массив;
Для Каждого Идентификатор Из Элементы.Товары.ВыделенныеСтроки Цикл
Строка = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
Строки.Добавить(Новый Структура("Номенклатура, Количество",
Строка.Номенклатура, Строка.Количество));
КонецЦикла;
ОбработатьНаСервере(Строки);
КонецПроцедуры
Как вернуть изменённые строки обратно в форму
Сервер без контекста не может трогать форму. Поэтому он возвращает массив структур, а клиент раскладывает его по строкам:
Ответ = РассчитатьСкидкиНаСервере(Строки);
Для Каждого Данные Из Ответ Цикл
Строка = Объект.Товары[Данные.НомерСтроки - 1];
Строка.Скидка = Данные.Скидка;
КонецЦикла;
Индекс строки на единицу меньше её номера: первая строка — Объект.Товары[0], у неё НомерСтроки = 1.
Способ 3. Передача ссылки и чтение табличной части из базы
Если документ уже записан и форма не менялась, табличную часть передавать вообще не нужно. Достаточно ссылки: сервер сам прочитает строки запросом.
&НаСервереБезКонтекста
Функция ИтогПоДокументу(Знач Ссылка)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СУММА(Товары.Сумма) КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Возврат ?(Выборка.Следующий(), Выборка.Сумма, 0);
КонецФункции
Вызов с клиента: Итог = ИтогПоДокументу(Объект.Ссылка);
Ограничения способа:
- 📌 У нового, ещё не записанного документа ссылка пустая — в базе нет ни одной строки.
- ✏️ Если пользователь изменил строки, но не записал документ, сервер увидит старую версию из базы, а не то, что на экране. Проверяйте
Модифицированностьформы перед таким вызовом. - 🔒 Сам объект (
ДокументОбъект) на клиент вернуть нельзя: он существует только на сервере.
Этот способ хорош для печати, отчётов по документу и проверок, которые должны опираться на записанные данные, а не на черновик в форме.
Способ 4. Временное хранилище для больших таблиц
Когда строк тысячи, а данные нужны в нескольких серверных вызовах подряд, гонять их каждый раз параметром невыгодно. Удобнее один раз положить их во временное хранилище и дальше передавать только строку-адрес.
// На сервере: подготовили таблицу и сохранили её
&НаСервере
Процедура ПодготовитьДанные()
Таблица = Объект.Товары.Выгрузить();
АдресДанных = ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
КонецПроцедуры
// В следующем вызове: получили таблицу по адресу
&НаСервереБезКонтекста
Процедура ОбработатьПоАдресу(Знач Адрес)
Таблица = ПолучитьИзВременногоХранилища(Адрес);
// работа с таблицей значений
КонецПроцедуры
АдресДанных — строковый реквизит формы. Второй параметр УникальныйИдентификатор привязывает хранилище к форме: данные живут, пока форма открыта. Без него значение может удалиться уже после ближайшего серверного вызова, и следующая процедура получит пустоту.
Где временное хранилище действительно выручает:
- ⏱️ Фоновые задания. Длительная обработка табличной части в фоне получает адрес и читает данные сама, форма не зависает.
- 📂 Загрузка из файла. Файл с клиента помещается в хранилище, сервер разбирает его и возвращает адрес готовой таблицы.
- 🔁 Многошаговые мастера. Данные одного шага переходят на следующий без повторной передачи.
Во временное хранилище помещайте только сериализуемые значения. Таблица значений подходит, ДокументОбъект или Запрос — нет.
Глобальные переменные и параметры сеанса: почему это не канал передачи
Иногда пытаются «положить таблицу в глобальную переменную» на клиенте и прочитать её на сервере. Так не работает .
- 🖥️ Переменные модуля управляемого приложения (
Перем Имя Экспорт) живут только на клиенте. На сервере их не видно. - 🗄️ Переменные модуля формы тоже клиентские. Видны на сервере только реквизиты формы — и то лишь при контекстном вызове.
- 🔑 Параметры сеанса устанавливаются на сервере, предназначены для общих настроек сеанса (текущий пользователь, организация по умолчанию) и не подходят для таблиц строк документа.
Если данные нужны нескольким формам в рамках сеанса, используйте временное хранилище или сохраните их в базе. Параметры сеанса для этого не предназначены.
Проверка данных и обработка ошибок на сервере
Всё, что пришло с клиента, сервер должен проверять заново. Клиентская проверка удобна пользователю, но не защищает: строки могли изменить, а тип значения в структуре мог оказаться не тем.
Что проверять в серверной процедуре:
- Тип и заполненность.
ЗначениеЗаполнено(Данные.Номенклатура)иТипЗнч(Данные.Количество) = Тип("Число"). - Наличие ключей структуры.
Данные.Свойство("Количество")до обращения к полю — иначе исключение. - Права. Запрос на сервере выполняется с правами пользователя, но привилегированный режим общего модуля снимает это ограничение. Не включайте его без необходимости.
Ошибки возвращайте структурой, а не одним исключением на всю таблицу. Тогда пользователь увидит все проблемные строки сразу:
Результат = Новый Массив;
Для Каждого Данные Из Строки Цикл
Если Не ЗначениеЗаполнено(Данные.Номенклатура) Тогда
Результат.Добавить(Новый Структура("НомерСтроки, Текст",
Данные.НомерСтроки,
СтрШаблон("Строка %1: не заполнена номенклатура", Данные.НомерСтроки)));
КонецЕсли;
КонецЦикла;
Возврат Результат;
При записи документа обычная проверка уже есть: процедура ОбработкаПроверкиЗаполнения в модуле объекта плюс флаги «Проверка заполнения» у реквизитов табличной части. Не дублируйте её своим кодом в форме.
Попытка … Исключение оставляйте для мест, где ошибка возможна по внешним причинам: запись, блокировка, обращение к внешнему сервису. Внутри блока Исключение используйте ОписаниеОшибки(), а при транзакции — отменяйте её до выхода.
Как ускорить передачу и запись табличной части
Скорость упирается в число вызовов и объём данных. Несколько приёмов, которые дают заметный эффект:
| Приём | Что даёт |
|---|---|
| Один вызов на всю таблицу вместо вызова на строку | Убирает сотни сетевых обращений |
&НаСервереБезКонтекста вместо &НаСервере | Не гоняет остальные реквизиты формы |
Знач у параметров | Данные не едут обратно на клиент |
| Только нужные колонки в структуре | Меньше объём сериализации |
| Запрос с таблицей-параметром вместо обращения к реквизитам через точку в цикле | Одно чтение из базы вместо N |
Запись строк в документ на сервере
Если сервер должен сам записать документ по пришедшим строкам, заполняйте табличную часть объекта целиком и записывайте один раз:
&НаСервереБезКонтекста
Процедура ЗаписатьСтроки(Знач Ссылка, Знач Строки)
ДокументОбъект = Ссылка.ПолучитьОбъект();
ДокументОбъект.Товары.Очистить();
Для Каждого Данные Из Строки Цикл
ЗаполнитьЗначенияСвойств(ДокументОбъект.Товары.Добавить(), Данные);
КонецЦикла;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецПроцедуры
Если на сервере уже есть таблица значений с совпадающими именами колонок, вместо цикла можно вызвать ДокументОбъект.Товары.Загрузить(Таблица).
Не вызывайте Записать() внутри цикла по строкам. Каждая запись — это транзакция, проверки и движения. Для документа на 500 строк это 500 записей вместо одной.
Если этот же документ открыт в форме у пользователя, после серверной записи форма покажет устаревшие данные. Перечитайте её: Прочитать() в контекстной процедуре или закройте и откройте форму заново.
&НаСервере, при необходимости РеквизитФормыВЗначение и ЗначениеВРеквизитФормы.&НаСервереБезКонтекста, параметры с Знач.Объект.Ссылка и прочитайте строки на сервере запросом.Частые вопросы
Можно ли передать на сервер таблицу значений с клиента?
ТаблицаЗначений недоступен. Соберите массив структур на клиенте и преобразуйте его в таблицу на сервере.Можно ли вернуть с сервера на клиент объект документа?
ДокументОбъект существует только на сервере. Верните ссылку, структуру с нужными полями или используйте ЗначениеВРеквизитФормы в контекстном вызове.