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