Таблица значений между клиентом и сервером: рабочие схемы с примерами кода

· 9 мин

Содержание

Таблицу значений нельзя передать с клиента на сервер как есть: в тонком клиенте и веб-клиенте типа ТаблицаЗначений просто нет. Передают её данные. Клиент упаковывает строки в массив структур или хранит их в реквизите формы, а сервер собирает из них настоящую таблицу значений. Для больших объёмов клиент держит только адрес во временном хранилище.

Всё ниже относится к управляемому приложению на платформе 8.3 и одинаково работает в любой конфигурации: БП 3.0, ЗУП 3.1, УТ 11 или ваша собственная разработка.

Почему таблица значений не проходит через границу клиент — сервер

Клиентский и серверный код в управляемом приложении выполняются в разных контекстах. Всё, что уходит в вызов сервера и возвращается обратно, платформа сериализует. Передать можно только те типы, которые существуют на обеих сторонах.

ТаблицаЗначений доступна на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте её нет. Поэтому код вида Таблица = Новый ТаблицаЗначений; в процедуре с директивой &НаКлиенте не скомпилируется, а серверная функция, которая возвращает таблицу значений, упадёт при вызове с тонкого клиента .

Что можно передавать между клиентом и сервером:

  • 🔢 примитивы: Строка, Число, Дата, Булево, Неопределено;
  • 🔗 ссылки на объекты базы (СправочникСсылка, ДокументСсылка и т. п.);
  • 📦 универсальные коллекции: Массив, Структура, Соответствие и их фиксированные варианты, если внутри них тоже допустимые типы;
  • 🧾 данные формы: ДанныеФормыКоллекция, ДанныеФормыСтруктура — это клиентское «отражение» таблиц и объектов;
  • 🗂 строку-адрес во временном хранилище.

В толстом клиенте обычного приложения таблица значений существует и на клиенте. Если конфигурация должна работать и в тонком клиенте, на это не опирайтесь: пишите так, будто клиент тонкий.

Схема преобразования: что происходит с данными по пути

Любой способ передачи сводится к трём действиям:

  1. На клиенте данные приводят к допустимому типу: массив структур, коллекция формы или адрес в хранилище.
  2. Вызов сервера переносит этот «контейнер». Платформа копирует его, а не передаёт по ссылке.
  3. На сервере из контейнера заново создают ТаблицаЗначений: добавляют колонки с типами и переносят строки.

Обратный путь зеркальный. Сервер превращает таблицу в массив структур, загружает её в реквизит формы или кладёт во временное хранилище.

СпособЧто держит клиентКогда подходитМинусы
Массив структурМассив из СтруктураСотни и тысячи строк, вызов без контекстаТипы колонок теряются, их задают заново на сервере
Реквизит формыДанныеФормыКоллекцияТаблица выводится на форму, пользователь её правитВызов &НаСервере тянет все данные формы
Временное хранилищеСтроку-адресБольшие таблицы, многократные вызовыНужно следить за временем жизни значения

Клиентский код: упаковываем строки для отправки

Самый частый случай — на форме документа есть табличная часть Товары, и её строки нужно обработать на сервере без передачи всей формы.

&НаКлиенте
Процедура ОтправитьНаСервер(Команда)
	
	Строки = Новый Массив;
	
	Для Каждого СтрокаТЧ Из Объект.Товары Цикл
		ОписаниеСтроки = Новый Структура("Номенклатура, Количество, Цена",
			СтрокаТЧ.Номенклатура, СтрокаТЧ.Количество, СтрокаТЧ.Цена);
		Строки.Добавить(ОписаниеСтроки);
	КонецЦикла;
	
	Итог = ОбработатьСтрокиНаСервере(Строки);
	
	Сообщить(СтрШаблон("Обработано строк: %1, сумма: %2",
		Итог.Количество, Формат(Итог.Сумма, "ЧДЦ=2")));
	
КонецПроцедуры

Если таблиц несколько, удобно вынести упаковку в универсальную клиентскую функцию. Ей передают коллекцию и список колонок через запятую:

&НаКлиенте
Функция КоллекцияВМассив(Коллекция, ИменаКолонок)
	
	Результат = Новый Массив;
	
	Для Каждого Строка Из Коллекция Цикл
		Элемент = Новый Структура(ИменаКолонок);
		ЗаполнитьЗначенияСвойств(Элемент, Строка);
		Результат.Добавить(Элемент);
	КонецЦикла;
	
	Возврат Результат;
	
КонецФункции

ЗаполнитьЗначенияСвойств заполняет только те ключи, которые уже есть в структуре. Лишние колонки табличной части в массив не попадут, и по сети уйдёт меньше данных.

Передавайте ссылки, а не представления. Строка «Гвозди 100 мм» на сервере бесполезна, а СправочникСсылка.Номенклатура передаётся без проблем и весит немного.

Серверный код: собираем таблицу значений заново

Из массива структур

На сервере таблицу создают с явными типами колонок. Без типов не будет работать передача таблицы в запрос и корректный подсчёт итогов.

&НаСервереБезКонтекста
Функция ОбработатьСтрокиНаСервере(Знач Строки)
	
	Таблица = Новый ТаблицаЗначений;
	Таблица.Колонки.Добавить("Номенклатура",
		Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
	Таблица.Колонки.Добавить("Количество",
		Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
	Таблица.Колонки.Добавить("Цена",
		Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
	Таблица.Колонки.Добавить("Сумма",
		Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
	
	Для Каждого ОписаниеСтроки Из Строки Цикл
		НоваяСтрока = Таблица.Добавить();
		ЗаполнитьЗначенияСвойств(НоваяСтрока, ОписаниеСтроки);
		НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;
	КонецЦикла;
	
	Результат = Новый Структура;
	Результат.Вставить("Количество", Таблица.Количество());
	Результат.Вставить("Сумма", Таблица.Итог("Сумма"));
	
	Возврат Результат;
	
КонецФункции

Пример: три строки 2 × 150, 5 × 40 и 1 × 1 000. Функция вернёт Количество = 3 и Сумма = 1 500 (300 + 200 + 1 000).

Обратите внимание на Знач у параметра. Изменения массива на сервере всё равно не вернутся на клиент, а Знач делает это явным для того, кто будет читать код.

Из реквизита формы

Если таблица живёт в реквизите формы, ничего упаковывать не нужно. Процедура с директивой &НаСервере видит данные формы, а методы формы превращают их в таблицу значений и обратно:

&НаСервере
Процедура СвернутьТаблицуНаСервере()
	
	Таблица = РеквизитФормыВЗначение("ТаблицаДанных");
	Таблица.Свернуть("Номенклатура", "Количество");
	ЗначениеВРеквизитФормы(Таблица, "ТаблицаДанных");
	
КонецПроцедуры

Для табличной части объекта используйте методы коллекции формы. Они доступны только на сервере:

&НаСервере
Процедура ПересчитатьТоварыНаСервере()
	
	Таблица = Объект.Товары.Выгрузить();
	// ... обработка таблицы значений ...
	Объект.Товары.Загрузить(Таблица);
	
КонецПроцедуры

После возврата с сервера платформа сама переносит изменённые данные формы на клиент. Это удобно, но об этом стоит помнить, когда таблица большая (об этом — в следующем разделе).

Большие таблицы: как не утонуть в вызовах сервера

Каждый вызов сервера — это сериализация, передача по сети и разбор на другой стороне. На локальной файловой базе разница почти незаметна. На сервере 1С через медленный канал или в веб-клиенте она становится главной причиной «зависаний» формы.

Что даёт реальный выигрыш:

  1. Один вызов вместо цикла вызовов. Обращение к серверу внутри Для Каждого по строкам — худший вариант. 5 000 строк дадут 5 000 обращений по сети. Соберите всё в один массив и вызовите сервер один раз.
  2. &НаСервереБезКонтекста вместо &НаСервере. Вызов с контекстом передаёт все данные формы туда и обратно, включая таблицы, которые вы не трогали. Без контекста уходят только параметры.
  3. Только нужные колонки. Если серверу нужны номенклатура и количество, не отправляйте десять колонок с характеристиками, комментариями и суммами.
  4. Только изменённые строки. При повторной отправке передавайте строки, которые пользователь правил, а не всю таблицу.
  5. Адрес вместо данных. Если таблица нужна серверу несколько раз за жизнь формы, положите её во временное хранилище один раз и передавайте адрес.

Схема с временным хранилищем выглядит так:

&НаСервереБезКонтекста
Функция ПодготовитьДанные(ИдентификаторФормы)
	
	Запрос = Новый Запрос(
		"ВЫБРАТЬ
		|	Номенклатура.Ссылка КАК Номенклатура,
		|	Номенклатура.Наименование КАК Наименование
		|ИЗ
		|	Справочник.Номенклатура КАК Номенклатура");
	
	Таблица = Запрос.Выполнить().Выгрузить();
	
	Возврат ПоместитьВоВременноеХранилище(Таблица, ИдентификаторФормы);
	
КонецФункции

&НаСервереБезКонтекста
Функция КоличествоСтрок(Адрес)
	
	Таблица = ПолучитьИзВременногоХранилища(Адрес);
	Возврат Таблица.Количество();
	
КонецФункции

На клиенте:

&НаКлиенте
Процедура ЗагрузитьДанные(Команда)
	
	АдресТаблицы = ПодготовитьДанные(УникальныйИдентификатор);
	Сообщить(СтрШаблон("Строк в таблице: %1", КоличествоСтрок(АдресТаблицы)));
	
КонецПроцедуры

АдресТаблицы — реквизит формы типа «Строка». Таблица значений ни разу не покидает сервер, по сети гуляет только короткая строка.

Время жизни значения зависит от второго параметра ПоместитьВоВременноеХранилище. С уникальным идентификатором формы значение хранится, пока форма открыта. Без него оно удаляется вскоре после завершения вызова сервера. В функции без контекста формы УникальныйИдентификатор недоступен, поэтому его передают параметром, как в примере.

💡 Когда данные больше не нужны, освободите память явно: УдалитьИзВременногоХранилища(АдресТаблицы) в обработчике закрытия формы. Для таблиц на десятки тысяч строк это заметно.

Какой способ выбрать и чем ещё можно обмениваться

Где живут данные и что с ними делают?

Кроме трёх основных схем есть ещё несколько механизмов. Они нужны реже, но знать их полезно:

  • 🧊 Фиксированные коллекции. ФиксированныйМассив и ФиксированнаяСтруктура защищают переданные данные от случайного изменения. Удобно для настроек, которые сервер отдаёт клиенту только на чтение.
  • 🗺 Соответствие. Подходит, когда нужен быстрый поиск по ключу, например «номенклатура → остаток». Передаётся между клиентом и сервером так же, как структура.
  • 🔤 Строка внутреннего формата. ЗначениеВСтрокуВнутр и ЗначениеИзСтрокиВнутр работают на сервере. Таблицу можно превратить в строку и отдать клиенту, но это громоздко и проигрывает временному хранилищу. Оправдано, если строку нужно сохранить, например в реквизит объекта.
  • 📄 JSON. Объекты ЗаписьJSON и ЧтениеJSON есть и на клиенте, но напрямую пишут только примитивы, массивы и структуры. Для обмена внутри одной базы это лишний слой, для интеграций — нормальный формат.
  • ⏳ Фоновое задание. Если обработка таблицы долгая, сервер запускает фоновое задание, а результат кладёт во временное хранилище. Клиент периодически проверяет готовность по адресу и не блокирует пользователя.

Ошибки при передаче таблицы значений и что с ними делать

Диагностика: выберите симптом

Ещё одна неочевидная ловушка — колонки с составным типом. Если в колонке могут лежать и ссылки, и числа, при сборке таблицы на сервере перечислите все типы в ОписаниеТипов через запятую: Новый ОписаниеТипов("Число, СправочникСсылка.Номенклатура"). Иначе значения не того типа приведутся к типу колонки, и вы получите нули или пустые ссылки без всякой ошибки.

Частые вопросы

Можно ли передать таблицу значений в параметрах открываемой формы?
С клиента — нет, по той же причине: типа на клиенте нет. Положите таблицу во временное хранилище на сервере и передайте в параметрах формы адрес. Открытая форма прочитает таблицу в ПриСозданииНаСервере.
Передаются ли ссылки внутри массива структур?
Да. Ссылки на справочники, документы и перечисления доступны на клиенте и передаются без преобразований. Нельзя передать сами объекты, например СправочникОбъект: их получают на сервере по ссылке.
Чем РеквизитФормыВЗначение отличается от Выгрузить?
РеквизитФормыВЗначение превращает в значение реквизит формы целиком по имени. Выгрузить работает с коллекцией формы, например Объект.Товары, и умеет выгружать только нужные колонки или строки.
Нужна ли директива &НаСервере, если таблица уже в реквизите формы?
Да, если сервер должен прочитать изменения пользователя. Вызов без контекста реквизитов формы не видит, поэтому либо вызывайте с контекстом, либо передавайте нужные строки параметром.
Есть ли ограничение на число строк при передаче?
Жёсткого лимита в строках нет, упираетесь в объём памяти и скорость канала. Если форма заметно тормозит на вызове, переходите на временное хранилище и передавайте только нужные колонки.