В управляемом приложении 1С разработчики часто сталкиваются с ситуацией, когда на стороне клиента необходимо прочитать локальный файл с динамическим набором колонок (например, CSV, XML или Excel), частично обработать его и передать сформированные строки на сервер. Основная сложность заключается в том, что универсальный объект ТаблицаЗначений доступен исключительно на сервере, в толстом клиенте и во внешнем соединении, а в контексте тонкого и веб-клиента его использование запрещено платформой.
Рассмотрим причины такого архитектурного ограничения, разберем типичные ошибки при попытке эмуляции таблицы значений через реквизиты управляемой формы и изучим наиболее производительные способы решения этой задачи.
Архитектура клиент-серверного взаимодействия в 1С ориентирована на работу тонкого и веб-клиента в условиях ограниченных ресурсов и медленных каналов связи. Выясним основные причины отсутствия серверных коллекций на клиенте:
ТаблицаЗначений содержит методы агрегации (Свернуть, Скопировать, НайтиСтроки, Итог) и может содержать ссылки на объекты базы данных. Сортировка или получение строковых представлений по ссылочным полям на клиенте вызывали бы неконтролируемые обращения к базе данных.Посмотрим на частый вариант реализации: добавление динамического реквизита формы типа ТаблицаЗначений, который на клиенте представляется объектом ДанныеФормыКоллекция.
Важно помнить: любые операции по добавлению, удалению или изменению структуры реквизитов формы должны производиться только на сервере через метод ИзменитьРеквизиты. Попытка вызвать конструктор Новый РеквизитФормы на клиенте приведет к ошибке.
Разберем пример создания таблицы формы и колонок на сервере:
&НаСервере
Процедура СоздатьТаблицуИКолонкиНаСервере(ИмяТаблицы, МассивОписанийКолонок)
ДобавляемыеРеквизиты = Новый Массив;
// 1. Создаем реквизит-таблицу
ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы(
ИмяТаблицы,
Новый ОписаниеТипов("ТаблицаЗначений"),
,
,
Истина
));
// 2. Создаем реквизиты-колонки в едином массиве
Для Каждого Колонка Из МассивОписанийКолонок Цикл
ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы(
Колонка.Имя,
Колонка.Тип,
ИмяТаблицы,
Колонка.Заголовок,
Истина
));
КонецЦикла;
// Пакетное применение изменений за один вызов
ИзменитьРеквизиты(ДобавляемыеРеквизиты);
КонецПроцедуры
Обратим внимание на критическую ошибку производительности: метод ИзменитьРеквизиты выполняет полную перекомпиляцию структуры формы. Если вызывать добавление реквизита для каждой колонки по отдельности из клиентского цикла, это приведет к десяткам или сотням серверных вызовов и зависанию формы. Описания всех колонок необходимо собирать в массив и передавать в ИзменитьРеквизиты единоразово.
После создания структуры на сервере мы можем обращаться к строкам таблицы на клиенте через синтаксис ЭтаФорма[ИмяТаблицы]:
&НаКлиенте
Процедура ЗаполнитьДанныеНаКлиенте(ИмяТаблицы)
КоллекцияФормы = ЭтаФорма[ИмяТаблицы];
НоваяСтрока = КоллекцияФормы.Добавить();
НоваяСтрока["Колонка1"] = "Тестовое значение";
КонецПроцедуры
Если данные не требуется выводить пользователю на экран в элементы управления формы, создавать динамические реквизиты не имеет смысла. Оптимальнее разобрать файл на клиенте в легковесную коллекцию, передать ее в серверную функцию и там инициализировать полноценную таблицу значений.
Проанализируем передачу данных с помощью массива структур:
&НаКлиенте
Процедура РазобратьФайлИОтправитьНаСервер()
// Сбор разобранных ячеек на клиенте
ТаблицаДанных = Новый Массив;
// В цикле парсинга файла заполняем массив координат и значений
Для Стр = 1 По 100 Цикл
Для Кол = 1 По 10 Цикл
СтрокаЯчейки = Новый Структура("НомерСтроки, НомерКолонки, Значение");
СтрокаЯчейки.НомерСтроки = Стр;
СтрокаЯчейки.НомерКолонки = Кол;
СтрокаЯчейки.Значение = "Данные_" + Стр + "_" + Кол;
ТаблицаДанных.Добавить(СтрокаЯчейки);
КонецЦикла;
КонецЦикла;
// Отправка на сервер одним вызовом
СформироватьТЗНаСервере(ТаблицаДанных);
КонецПроцедуры
&НаСервереБезКонтекста
Функция СформироватьТЗНаСервере(ТаблицаДанных)
ТЗ = Новый ТаблицаЗначений;
Для Каждого Элемент Из ТаблицаДанных Цикл
Пока Элемент.НомерКолонки > ТЗ.Колонки.Количество() Цикл
ТЗ.Колонки.Добавить("Колонка" + (ТЗ.Колонки.Количество() + 1));
КонецЦикла;
Пока Элемент.НомерСтроки > ТЗ.Количество() Цикл
ТЗ.Добавить();
КонецЦикла;
ТЗ[Элемент.НомерСтроки - 1][Элемент.НомерКолонки - 1] = Элемент.Значение;
КонецЦикла;
Возврат ТЗ;
КонецФункции
Рассмотрим ситуацию с большими файлами. При передаче массива структур платформа сериализует имена ключей структуры (НомерСтроки, НомерКолонки, Значение) для каждой строки, что увеличивает объем передаваемого трафика. Для матричных структур намного эффективнее использовать вложенные массивы (массив строк, где каждая строка — массив колонок):
&НаКлиенте
Функция ПрочитатьМатрицуВМассив()
МатрицаДанных = Новый Массив;
Для Стр = 1 По КоличествоСтрок Цикл
СтрокаМассива = Новый Массив;
Для Кол = 1 По КоличествоКолонок Цикл
СтрокаМассива.Добавить(ПолучитьЗначениеЯчейки(Стр, Кол));
КонецЦикла;
МатрицаДанных.Добавить(СтрокаМассива);
КонецЦикла;
Возврат МатрицаДанных;
КонецФункции
Такой подход минимизирует накладные расходы на сериализацию и сокращает время передачи данных с клиента на сервер.
Если файл содержит большой объем информации, передача громоздких коллекций через параметры серверного вызова может вызывать задержки. В этом случае эффективно упаковать данные в текстовую строку формата JSON с помощью потокового объекта ЗаписьJSON:
&НаКлиенте
Функция СформироватьJSONДанных(МатрицаДанных)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, МатрицаДанных);
СтрокаJSON = ЗаписьJSON.Закрыть();
Возврат СтрокаJSON;
КонецФункции
&НаСервереБезКонтекста
Процедура ПринятьДанныеJSON(СтрокаJSON)
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
МатрицаДанных = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Дальнейшая обработка массива на сервере
КонецПроцедуры
Если на сервере сформирована ТаблицаЗначений и ее необходимо быстро отобразить в существующем реквизите формы типа ДанныеФормыКоллекция (или наоборот), не следует копировать строки поэлементно через цикл. Для этого используем встроенные методы платформы:
&НаСервере
Процедура ЗагрузитьТЗВРеквизитФормы(СервернаяТЗ)
// Быстрая выгрузка ТЗ в реквизит формы
ЗначениеВДанныеФормы(СервернаяТЗ, ЭтаФорма.РеквизитТаблица);
КонецПроцедуры
&НаСервере
Функция ПолучитьТЗИзРеквизитаФормы()
// Быстрое получение серверной ТаблицыЗначений из реквизита формы
СервернаяТЗ = ДанныеФормыВЗначение(ЭтаФорма.РеквизитТаблица, Тип("ТаблицаЗначений"));
Возврат СервернаяТЗ;
КонецФункции
XBase не поддерживается на тонком клиенте. Если требуется обработка DBF или тяжелый парсинг, файл следует передавать на сервер как двоичные данные через временное хранилище и выполнять разбор непосредственно на сервере.ТабличныйДокумент, который поддерживает чтение файлов таблиц через метод Прочитать(). Из него данные легко считываются по координатам ячеек.