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