Как правильно передать Таблицу Значений с клиента на сервер в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие 8.3 с использованием управляемых форм мы часто сталкиваемся с архитектурными ограничениями, продиктованными разделением кода на клиентскую и серверную части. Одной из самых распространенных проблем, с которой сталкиваются как начинающие, так и опытные разработчики, является необходимость передачи табличных данных (например, списка товаров, подобранных пользователем, или загруженных из внешнего файла данных) с клиента на сервер для дальнейшей обработки.

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

Понимаем суть проблемы: Клиент-Сервер и Мутабельные значения

Прежде чем приступать к написанию кода, давайте проанализируем, почему платформа ведет себя именно так. В архитектуре управляемого приложения (Тонкий клиент, Веб-клиент) существует четкое разделение типов данных:

  1. Типы, доступные на клиенте и сервере: Числа, Строки, Даты, Булево, Массивы, Структуры, ФиксированныеМассивы и некоторые другие. Эти данные легко сериализуются и передаются по сети.
  2. Типы, доступные только на сервере: ТаблицаЗначений, ДеревоЗначений, Запрос, справочники и документы (как объекты).

Объект ТаблицаЗначений является сложным серверным объектом. Он не существует на Тонком клиенте. То, что мы видим в форме (например, в табличной части) — это тип ДанныеФормыКоллекция, который является "проекцией" таблицы, адаптированной для интерфейса.

Когда мы пытаемся передать сложную коллекцию с клиента на сервер, платформа должна "упаковать" (сериализовать) эти данные, передать их через сеть (или имитацию сети в файловом варианте) и "распаковать" на сервере. Для оптимизации трафика и производительности 1С запрещает прямую передачу тяжелых, изменяемых (мутабельных) объектов, к которым относится Таблица Значений.

Рассмотрим, какие существуют эффективные методы обхода этого ограничения. Мы выделим два основных подхода, которые наиболее часто рекомендуются в профессиональном сообществе и документации.

Решение 1: Использование Массива Структур

Это наиболее универсальный и "легкий" способ, идеально подходящий для передачи небольших и средних объемов данных. Идея заключается в преобразовании табличных данных в примитивные коллекции, которые платформа умеет передавать без ошибок.

Алгоритм действий следующий:

  1. На стороне клиента мы создаем Массив.
  2. Проходим циклом по строкам нашей клиентской коллекции (обычно это табличная часть формы или реквизит формы).
  3. Каждую строку превращаем в Структуру, где ключи — это имена колонок, а значения — данные ячеек.
  4. Добавляем эту структуру в массив.
  5. Передаем массив в серверную процедуру.
  6. На сервере создаем новую ТаблицаЗначений и заполняем её данными из полученного массива.

Рассмотрим пример реализации этого подхода. Предположим, у нас на форме есть таблица Товары с колонками Номенклатура и Количество.

Код на Клиенте

Сначала подготовим данные на клиенте. Создадим функцию, которая собирает данные в массив.


&НаКлиенте
Процедура ВыполнитьОбработкуТоваров(Команда)
    
    // Создаем массив для передачи данных
    МассивДанных = Новый Массив;
    
    // Проходим по данным формы (Коллекция данных формы)
    Для Каждого СтрокаТЧ Из Объект.Товары Цикл
        
        // Создаем структуру для хранения данных одной строки
        // Ключи структуры должны совпадать с именами будущих колонок ТЗ
        ДанныеСтроки = Новый Структура;
        ДанныеСтроки.Вставить("Номенклатура", СтрокаТЧ.Номенклатура);
        ДанныеСтроки.Вставить("Количество",   СтрокаТЧ.Количество);
        
        // Добавляем структуру в массив
        МассивДанных.Добавить(ДанныеСтроки);
        
    КонецЦикла;
    
    // Вызываем серверную процедуру и передаем туда массив
    ОбработатьДанныеНаСервере(МассивДанных);
    
КонецПроцедуры

Обратите внимание: мы используем объект Структура, так как он прекрасно передается между клиентом и сервером. Важно следить за тем, чтобы значения внутри структуры также были сериализуемыми (например, ссылки на справочники, числа и строки передаются без проблем, а вот универсальные коллекции внутри структуры могут вызвать сложности).

Код на Сервере

Теперь примем эти данные на стороне сервера и преобразуем их обратно в полноценную Таблицу Значений для дальнейшей работы (например, для использования в запросе).


&НаСервере
Процедура ОбработатьДанныеНаСервере(МассивДанных)
    
    // Создаем новую Таблицу Значений
    ТЗ = Новый ТаблицаЗначений;
    
    // Определяем колонки. Важно типизировать их, если планируется
    // использование в построителях запроса или СКД, но для простых
    // операций можно оставить без типизации.
    ТЗ.Колонки.Добавить("Номенклатура");
    ТЗ.Колонки.Добавить("Количество");
    
    // Заполняем таблицу данными из массива
    Для Каждого ЭлементМассива Из МассивДанных Цикл
        
        НоваяСтрока = ТЗ.Добавить();
        
        // Заполняем значения свойств. 
        // Метод ЗаполнитьЗначенияСвойств отлично подходит для переноса
        // данных из Структуры в СтрокуТЗ, если имена ключей и колонок совпадают.
        ЗаполнитьЗначенияСвойств(НоваяСтрока, ЭлементМассива);
        
    КонецЦикла;
    
    // Теперь у нас есть полноценная ТЗ, с которой можно работать
    // Например, передать её в Запрос как параметр
    // Запрос.УстановитьПараметр("ТЗ", ТЗ);
    
КонецПроцедуры

Этот метод хорош своей прозрачностью и простотой отладки. Вы всегда можете остановиться отладчиком и посмотреть содержимое массива.

Решение 2: Использование Временного Хранилища

Второй способ часто применяется, когда данных много, или когда таблица формируется где-то в недрах обработки и её нужно передать "транзитом". Временное хранилище (TempStorage) — это специальная область памяти на сервере, куда можно поместить данные и получить строковый адрес (UUID), по которому эти данные можно забрать в другом месте.

Однако, здесь есть нюанс: метод ПоместитьВоВременноеХранилище доступен и на клиенте, и на сервере. Но на клиенте мы не можем поместить туда ТаблицуЗначений, потому что её там просто не существует. Поэтому с клиента мы обычно помещаем туда тот же массив структур, либо файл.

Но если задача стоит так: "У нас есть данные в реквизите формы (ДанныеФормыКоллекция), и мы хотим получить их в серверном модуле без контекста формы", то подход с временным хранилищем может выглядеть так:

  1. Мы преобразуем данные формы в структуру или массив на сервере (в контекстном вызове).
  2. Помещаем их в хранилище.
  3. Передаем адрес в фоновое задание или другую процедуру.

Но давайте рассмотрим более хитрый прием, который часто используется для передачи именно таблиц. Если данные уже находятся в реквизите формы (например, табличная часть Товары), то при вызове серверной процедуры &НаСервере (с контекстом) платформа автоматически передает все данные формы на сервер. В этом случае нам не нужно ничего "передавать" параметрами.

Посмотрим на ситуацию, когда нам нужно получить именно объект ТаблицаЗначений из табличной части формы внутри серверной процедуры.


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

Важное замечание: Метод РеквизитФормыВЗначение доступен только в процедурах с контекстом (&НаСервере). Если вы используете &НаСервереБезКонтекста, этот метод недоступен, и доступа к Объект.Товары у вас тоже не будет. Именно в таких случаях (безконтекстные вызовы) мы возвращаемся к Решению 1 с массивом структур.

Оптимизация и производительность

Давайте проанализируем производительность обоих методов. Когда мы передаем массив структур:

Когда мы используем контекстный вызов и РеквизитФормыВЗначение:

Универсальная функция преобразования

Чтобы не писать циклы каждый раз, разработаем универсальную функцию. Поскольку мы пишем код, который должен работать с разными таблицами, полезно иметь в своем арсенале общего модуля или модуля формы готовый инструмент.

На клиенте мы не можем знать типы колонок заранее универсально без обращения к серверу, поэтому чаще всего конвертация происходит "по факту" наличия данных.

Посмотрим на пример универсальной функции для сервера, которая принимает массив структур и возвращает ТЗ. Это полезно вынести в Общий Модуль (например, ОбщегоНазначенияСервер).


// Функция в общем модуле (Сервер)
Функция ПреобразоватьМассивСтруктурВТЗ(МассивСтруктур) Экспорт
    
    ТЗ = Новый ТаблицаЗначений;
    
    Если МассивСтруктур.Количество() = 0 Тогда
        Возврат ТЗ;
    КонецЕсли;
    
    // Определяем структуру колонок по первой строке
    ПерваяСтрока = МассивСтруктур[0];
    Для Каждого КлючИЗначение Из ПерваяСтрока Цикл
        // Для точной типизации можно анализировать типы значений,
        // но здесь создадим произвольные колонки
        ТЗ.Колонки.Добавить(КлючИЗначение.Ключ);
    КонецЦикла;
    
    // Заполняем
    Для Каждого Элемент Из МассивСтруктур Цикл
        ЗаполнитьЗначенияСвойств(ТЗ.Добавить(), Элемент);
    КонецЦикла;
    
    Возврат ТЗ;
    
КонецФункции

Используя такую функцию, ваш код в форме станет значительно чище:


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

Как обработать ошибки XDTO?

Иногда, даже используя массив структур, вы можете получить ошибку Ошибка сериализации XDTO. Обычно это происходит, если вы пытаетесь передать в структуре тип данных, который сервер не может правильно интерпретировать из контекста клиента, или наоборот, если с сервера на клиент возвращается что-то специфическое.

Частые причины:

  1. В структуре содержится УникальныйИдентификатор, который в старых версиях платформы мог вызывать вопросы (сейчас обычно работает корректно).
  2. В структуре содержится другой сложный объект (например, вложенный массив массивов с неопределенными типами), который вызывает переполнение стека сериализации в редких случаях.
  3. Попытка передать COMОбъект.

Для исправления таких ошибок всегда проверяйте состав данных. Если нужно передать сложный объект, попробуйте сериализовать его в строку (например, через ЗначениеВСтрокуВнутр - но помните, что это работает только если приемник понимает этот формат, безопаснее использовать JSON).

Выводы

Подведем итог нашему исследованию. Передача таблицы значений с клиента на сервер — это стандартная задача, которая решается через промежуточные легкие коллекции.

  1. Используйте Массив Структур для передачи данных в безконтекстные серверные методы. Это самый производительный и чистый способ.
  2. Используйте РеквизитФормыВЗначение внутри контекстных серверных вызовов, если данные уже есть на форме и вы не боитесь передать контекст.
  3. Избегайте попыток передать ТаблицаЗначений как параметр — это архитектурно невозможно в 1С 8.3.

Применяя эти методы, вы обеспечите стабильную работу вашего приложения и избежите распространенных ошибок сериализации.

← На главную