При переходе на управляемые формы в системе «1С:Предприятие 8» разработчики часто сталкиваются со следующей проблемой: при попытке передать результат запроса или объект типа ТаблицаЗначений из серверной функции на клиент возвращается ошибка отображения типов, а конструктор Новый ТаблицаЗначений на клиенте вызывает ошибку «Тип не определен». В этой статье мы подробно разберем, почему возникает данная ситуация, рассмотрим теорию мутабельности данных и изучим четыре эффективных способа решения этой задачи в зависимости от ваших целей.
Для того чтобы понять природу ошибки, проанализируем архитектуру управляемого приложения. В 1С существует строгое разделение сред выполнения кода: &НаКлиенте (компьютер пользователя, тонкий или веб-клиент) и &НаСервере (сервер приложений 1С). Передача данных между ними происходит по сети посредством сериализации (превращения объектов в XML/JSON-подобный поток данных).
Платформа накладывает жесткое ограничение: мутабельные (изменяемые) типы данных нельзя передавать между клиентом и сервером. К таким типам относятся:
ТаблицаЗначений и ее строки;ДеревоЗначений;СправочникОбъект, ДокументОбъект);Передавать по сети разрешено только немутабельные (сериализуемые) типы: примитивные типы (число, строка, дата, булево), ссылки на объекты (ДокументСсылка, СправочникСсылка), а также коллекции, элементы которых сами являются сериализуемыми (например, Массив, Структура, Соответствие).
Попытка вызвать метод РезультатЗапроса.Выгрузить() на сервере и вернуть его на клиент напрямую приводит к краху программы, поскольку тип ТаблицаЗначений не может пересечь клиент-серверную границу. Выясним, как правильно обойти это ограничение.
Рассмотрим ситуацию, описанную автором темы: данные запроса нужны для того, чтобы произвести по ним расчеты и добавить строки в табличную часть текущего документа. В этом случае передавать таблицу на клиент вообще не требуется!
В управляемом приложении реквизит Объект (содержимое документа или справочника) доступен на сервере в контексте управляемой формы. Любые изменения, внесенные в Объект на сервере, автоматически синхронизируются с клиентом и отображаются на экране пользователя без дополнительных усилий.
Разберем по шагам правильный алгоритм реализации:
Посмотрим на пример программного кода, который реализует эту логику:
&НаКлиенте
Процедура ЗаполнитьИтогоИзМеню(Команда)
// Просто вызываем серверную процедуру, параметры передавать не нужно,
// так как реквизиты формы доступны на сервере в контексте.
ЗаполнитьТабличнуюЧастьНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьТабличнуюЧастьНаСервере()
// Инициализируем запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура,
| Количество,
| Цена
|ИЗ
| РегистрНакопления.ОстаткиТоваров КАК Остатки
|ГДЕ
| Остатки.Склад = &Склад";
Запрос.УстановитьПараметр("Склад", Объект.Склад);
РезультатЗапроса = Запрос.Выполнить();
// Очищаем существующую табличную часть документа на сервере
Объект.Товары.Очистить();
// Выгружаем и обходим выборку
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Добавляем строку непосредственно в табличную часть реквизита "Объект"
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
// Производим необходимые расчеты прямо на сервере
НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;
НоваяСтрока.СтавкаНДС = Справочники.СтавкиНДС.БезНДС;
КонецЦикла;
КонецПроцедуры
Этот способ является максимально производительным и архитектурно правильным. Мы не гоняем лишние данные по сети, а сервер выполняет вычисления гораздо быстрее клиентской машины.
Если вам все же необходимо получить данные запроса на клиенте (например, для интерактивного выбора пользователем или для отображения вспомогательных окон предупреждений), лучшим вариантом будет преобразование таблицы значений в Массив Структур.
Массив и структура — это немутабельные типы, которые отлично подходят для передачи по сети. Структура будет имитировать строку таблицы (ключ — имя колонки, значение — значение ячейки), а массив объединит эти строки в единую коллекцию.
Рассмотрим пример ручной упаковки таблицы значений в массив структур на сервере с последующей передачей на клиент:
&НаКлиенте
Процедура ПолучитьДанныеНаКлиент(Команда)
// Получаем сериализуемый массив структур с сервера
ДанныеДляКлиента = ПолучитьМассивСтруктурССервера();
// Обрабатываем данные на клиенте
Для Каждого СтрокаДанных Из ДанныеДляКлиента Цикл
ПоказатьПредупреждение(, "Товар: " + СтрокаДанных.Номенклатура + " на сумму: " + СтрокаДанных.Сумма);
КонецЦикла;
КонецПроцедуры
&НаСервере
Функция ПолучитьМассивСтруктурССервера()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура,
| Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары
|ГДЕ
| Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
ТЗ = Запрос.Выполнить().Выгрузить();
// Формируем массив структур
МассивРезультатов = Новый Массив;
Для Каждого СтрокаТЗ Из ТЗ Цикл
НоваяСтрока = Новый Структура;
// Заполняем структуру динамически на основе колонок таблицы значений
Для Каждого Колонка Из ТЗ.Колонки Цикл
НоваяСтрока.Вставить(Колонка.Имя, СтрокаТЗ[Колонка.Имя]);
КонецЦикла;
МассивРезультатов.Добавить(НоваяСтрока);
КонецЦикла;
Возврат МассивРезультатов;
КонецФункции
Важное примечание: Если в вашей конфигурации используется Библиотека Стандартных Подсистем (БСП), вам не нужно писать циклы обхода вручную. Вы можете воспользоваться готовой оптимизированной функцией БСП: ОбщегоНазначения.ТаблицаЗначенийВМассив(ВашаТЗ). Она написана на уровне платформы и работает значительно быстрее.
Если вам требуется визуализировать произвольные данные на форме, но при этом они не должны записываться в сам документ (то есть это временная экранная таблица), можно использовать реквизит управляемой формы.
Для реализации этого метода выполните следующие шаги:
ТаблицаЭкран) и выберите тип данных Таблица значений.Товар, Остаток).Теперь на сервере этот реквизит доступен как коллекция типа ДанныеФормыКоллекция. Мы можем преобразовывать ее в настоящую таблицу значений для сложных расчетов и загружать обратно:
&НаСервере
Процедура ЗаполнитьЭкраннуюТаблицуНаСервере()
// Получаем полноценную таблицу значений из реквизита формы для работы
ТаблицаОбъект = РеквизитФормыВЗначение("ТаблицаЭкран", Тип("ТаблицаЗначений"));
// Теперь мы можем использовать все методы ТаблицыЗначений (Поиск, Свернуть, ЗаполнитьЗначения)
// Например, добавим тестовую строку
НоваяСтрока = ТаблицаОбъект.Добавить();
НоваяСтрока.Товар = Справочники.Номенклатура.НайтиПоКоду("00000001");
НоваяСтрока.Остаток = 150;
// Загружаем измененную таблицу обратно в реквизит формы
ЗначениеВРеквизитФормы(ТаблицаОбъект, "ТаблицаЭкран");
// Данные автоматически отобразятся у пользователя на клиенте!
КонецПроцедуры
Обратите внимание, что при использовании метода ЗначениеВРеквизитФормы колонки в исходной ТаблицеЗначений должны строго совпадать по именам и типам данных с колонками реквизита формы, иначе система выдаст исключение.
Если таблица значений содержит десятки тысяч строк, ее передача на клиент в виде массива структур крайне нежелательна. Сериализация огромных объемов данных сильно нагрузит сеть, процессор сервера и заблокирует интерфейс пользователя.
Для таких сценариев идеальным решением служит Временное хранилище. Данные физически остаются в оперативной памяти сервера (или во временных файлах базы данных), а на клиент передается лишь ультракороткая строка-адрес.
Проанализируем пример реализации данного паттерна проектирования:
&НаКлиенте
Процедура ВыполнитьПередачуЧерезХранилище(Команда)
// Вызываем сервер для формирования данных и получаем адрес в хранилище
АдресДанных = СформироватьИПоместитьТаблицуВХранилище();
// Теперь у нас на клиенте есть уникальный адрес: "e1cib/tempstorage/..."
// Мы можем передать его в другую форму, в фоновое задание или использовать позже
ВыполнитьОбработкуНаСервере(АдресДанных);
КонецПроцедуры
&НаСервере
Функция СформироватьИПоместитьТаблицуВХранилище()
// Создаем тяжелую таблицу значений
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Идентификатор");
ТЗ.Колонки.Добавить("СлужебнаяИнформация");
Для Шаг = 1 По 50000 Цикл
Строка = ТЗ.Добавить();
Строка.Идентификатор = Шаг;
Строка.СлужебнаяИнформация = "Уникальные служебные данные строки № " + Шаг;
КонецЦикла;
// Помещаем таблицу во временное хранилище.
// Параметр ЭтаФорма.УникальныйИдентификатор гарантирует автоматическое очищение
// памяти на сервере сразу после закрытия пользователем данной управляемой формы.
АдресХранилища = ПоместитьВоВременноеХранилище(ТЗ, ЭтаФорма.УникальныйИдентификатор);
Возврат АдресХранилища;
КонецФункции
&НаСервереБезКонтекста
Процедура ВыполнитьОбработкуНаСервере(Знач АдресДанных)
// Извлекаем тяжелую таблицу на сервере по переданному адресу
ТаблицаИзХранилища = ПолучитьИзВременногоХранилища(АдресДанных);
Если ТипЗнч(ТаблицаИзХранилища) = Тип("ТаблицаЗначений") Тогда
// Проводим необходимые технологические операции
ЗаписьЖурналаРегистрации("Обработка данных",,,, "Обработано строк: " + ТаблицаИзХранилища.Количество());
КонецЕсли;
КонецПроцедуры
Мы рассмотрели все ключевые методы работы с таблицами данных в клиент-серверной архитектуре платформы 1С. Для закрепления материала выделим главное правило выбора оптимального инструмента:
Объект) — выполняйте всю работу на сервере в контексте формы без возвращения результатов на клиент.