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

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

Разработка в среде «1С:Предприятие 8.3» на управляемых формах накладывает жесткие правила на взаимодействие между клиентской и серверной частями приложения. Одной из классических проблем, с которой сталкиваются как начинающие, так и опытные разработчики, является попытка вернуть ТаблицаЗначений напрямую из серверной функции на клиент. Платформа реагирует на это ошибкой передачи данных XDTO и уведомлением о недопустимом типе данных.

В рамках этой статьи мы подробно разберем, почему возникает эта ошибка, проанализируем популярные (включая небезопасные) обходные пути и рассмотрим современные стандарты разработки для передачи табличных данных и работы с файлами Microsoft Excel.

Почему возникает ошибка передачи данных XDTO?

Причиной возникновения ошибки является архитектурное ограничение платформы «1С:Предприятие». Объект ТаблицаЗначений относится к так называемым мутабельным типам данных. К этой же категории относятся ДокументОбъект, СправочникОбъект и другие прикладные объекты базы данных.

Мутабельные объекты могут динамически изменять свое состояние, структуру и состав колонок. Их прямая передача по сети между сервером и клиентом заблокирована на уровне платформы по следующим причинам:

  1. Оптимизация сетевого трафика. Передача тяжелого объекта со всей его метасистемной информацией создает колоссальную нагрузку на каналы связи.
  2. Различие сред выполнения. В Тонком и Веб-клиентах объект типа ТаблицаЗначений физически отсутствует в памяти клиентского приложения. Попытка его сериализации вызывает сбой, так как клиент не знает, как воссоздать этот объект у себя.

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

Критика обходного решения через ЗначениеВСтрокуВнутр

В ходе обсуждения проблемы часто предлагается использовать «быстрое» решение с помощью системных функций преобразования во внутреннюю строку:


&НаКлиенте
Процедура Сформировать(Команда)
    // Попытка получить сериализованную строку и восстановить её
    ТаблицаЗначенийДанные = ЗначениеИзСтрокиВнутр(ПолучитьДанныеВТЗ());
КонецПроцедуры

&НаСервере
Функция ПолучитьДанныеВТЗ()
    Запрос = Новый Запрос;
    // ... Настройка и выполнение запроса ...
    ВыгруженныеДанные = РезультатЗапроса.Выгрузить();
    
    // Преобразование во внутренний системный формат
    Возврат ЗначениеВСтрокуВнутр(ВыгруженныеДанные);
КонецФункции

Внимание: данный способ является устаревшим и крайне опасным! Использовать его в современных конфигурациях категорически не рекомендуется по следующим причинам:

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

Способ 1. Использование массива структур (Рекомендовано БСП)

Если вам требуется обработать данные таблицы на клиенте, оптимальным решением является конвертация таблицы значений на сервере в массив простых структур. Структуры и массивы не являются мутабельными типами и отлично передаются на любой клиент (включая Тонкий, Мобильный и Веб-клиент).

Рассмотрим пример универсальной функции преобразования, аналогичной механизмам Библиотеки Стандартных Подсистем (БСП):


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

На стороне клиента вы получите обычный массив, по которому можно осуществлять обход в цикле:


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

Способ 2. Использование реквизитов формы

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

На стороне клиента этот реквизит автоматически преобразуется платформой в тип ДанныеФормыКоллекция. Данный тип оптимизирован для передачи между клиентом и сервером. Разберем по шагам, как выполнить заполнение:

  1. Создайте реквизит управляемой формы с именем ДанныеОтчета и типом ТаблицаЗначений. Добавьте необходимые колонки.
  2. Вызовите серверную процедуру для заполнения реквизита:

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

Платформа самостоятельно выполнит сериализацию и передаст заполненную коллекцию на клиент. На клиенте вы сможете работать с ней напрямую через переменную Объект.ДанныеОтчета (или просто ДанныеОтчета, если реквизит принадлежит форме, а не объекту).

Способ 3. Временное хранилище

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

Проанализируем этот сценарий:


&НаСервере
Функция ПолучитьДанныеВоВременноеХранилище()
    ТаблицаДанных = ПолучитьТаблицуИзЗапроса(); // Ваша серверная функция получения ТЗ
    
    // Помещаем таблицу в хранилище и привязываем к уникальному идентификатору формы
    АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДанных, УникальныйИдентификатор);
    Возврат АдресХранилища;
КонецФункции

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


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

Как правильно работать с Excel в современных реалиях?

В исходном обсуждении автор заполняет шаблон Excel построчно через COM-соединение на стороне клиента:

Эксель = Новый COMОбъект("Excel.Application");

Использование COM-объектов на клиенте или сервере в современных версиях 1С несет в себе критические архитектурные риски:

Современная альтернатива: Выполнение всей работы на стороне сервера

Для модификации сложных файлов (особенно отчетов государственных ведомств или Центробанка, где содержатся скрытые макросы и пароли на ячейках) лучшим решением будет перенос всей работы на серверную сторону с использованием двоичных данных.

Давайте выясним алгоритм действий:

  1. Передайте файл-шаблон с клиента на сервер через Временное хранилище в виде двоичных данных.
  2. На сервере сохраните двоичные данные во временный файл.
  3. Используйте серверные библиотеки чтения/записи Excel или, если сервер работает под управлением ОС Windows, выполните заполнение через серверный COM-объект без передачи таблиц значений на клиент.
  4. После заполнения файла преобразуйте его обратно в двоичные данные, верните на клиент и запишите на диск пользователя.

Рассмотрим пример серверного заполнения шаблона с последующим возвратом файла на клиент:


&НаКлиенте
Процедура ЗаполнитьИСохранитьШаблон(Команда)
    
    // Предположим, путь к файлу шаблона на клиенте уже выбран
    ПутьКФайлуШаблона = "C:\Templates\CB_Report.xlsx";
    ДвоичныеДанныеШаблона = Новый ДвоичныеДанные(ПутьКФайлуШаблона);
    
    // Помещаем файл во временное хранилище
    АдресФайлаВХранилище = ПоместитьВоВременноеХранилище(ДвоичныеДанныеШаблона, УникальныйИдентификатор);
    
    // Вызываем сервер для обработки файла
    АдресГотовогоФайла = ЗаполнитьФайлНаСервере(АдресФайлаВХранилище);
    
    // Получаем готовый файл из хранилища и сохраняем его у пользователя
    ГотовыеДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресГотовогоФайла);
    ПутьДляСохранения = "C:\Reports\CB_Report_Filled.xlsx";
    ГотовыеДвоичныеДанные.Записать(ПутьДляСохранения);
    
    Сообщить("Файл успешно заполнен и сохранен!");
    
КонецПроцедуры

&НаСервере
Функция ЗаполнитьФайлНаСервере(Знач АдресШаблона)
    
    // Получаем двоичные данные файла из хранилища
    ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресШаблона);
    
    // Получаем имя временного файла на сервере
    ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
    ДвоичныеДанныеФайла.Записать(ИмяВременногоФайла);
    
    // Получаем данные из базы данных для заполнения
    ТаблицаДанных = ПолучитьДанныеИзЗапроса(); 
    
    // Запускаем работу с Excel на стороне сервера (только если сервер работает под Windows)
    Попытка
        ExcelПриложение = Новый COMОбъект("Excel.Application");
        ExcelПриложение.DisplayAlerts = Ложь;
        
        Книга = ExcelПриложение.Workbooks.Open(ИмяВременногоФайла);
        Лист = Книга.Worksheets(1);
        
        // Построчное заполнение шаблона
        ИндексСтроки = 10;
        Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
            Лист.Cells(ИндексСтроки, 2).Value = СтрокаТЗ.Дата;
            Лист.Cells(ИндексСтроки, 3).Value = СтрокаТЗ.Номер;
            Лист.Cells(ИндексСтроки, 4).Value = СтрокаТЗ.СуммаОбщая;
            ИндексСтроки = ИндексСтроки + 1;
        КонецЦикла;
        
        Книга.Save();
        Книга.Close();
        ExcelПриложение.Quit();
    Исключение
        // Обязательно закрываем процесс Excel при ошибке, чтобы избежать утечек памяти
        Если Не ExcelПриложение = Неопределено Тогда
            ExcelПриложение.Quit();
        КонецЕсли;
        ВызватьИсключение "Ошибка заполнения Excel на сервере: " + ОписаниеОшибки();
    КонецПопытки;
    
    // Читаем измененный файл обратно в двоичные данные
    РезультатДвоичныеДанные = Новый ДвоичныеДанные(ИмяВременногоФайла);
    
    // Удаляем временный файл на сервере
    УдалитьФайлы(ИмяВременногоФайла);
    
    // Помещаем измененный файл в хранилище и возвращаем адрес клиенту
    Возврат ПоместитьВоВременноеХранилище(РезультатДвоичныеДанные, УникальныйИдентификатор);
    
КонецФункции

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

← На главную