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

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

При разработке в среде «1С:Предприятие» программисты часто сталкиваются с классической архитектурной проблемой платформы: попыткой передачи мутабельного значения между клиентом и сервером. Типичная ситуация — необходимость использовать один и тот же алгоритм заполнения табличной части (ТЧ) документа из двух разных мест: из клиентской обработки (в контексте толстого клиента) и из серверного процесса (например, при автоматическом формировании производственного плана).

Если попытаться передать ДокументОбъект напрямую в серверный общий модуль из клиента, платформа выбросит исключение: «Ошибка передачи данных между клиентом и сервером. Мутабельное значение...». Выясним причину этой ошибки и разберем по шагам несколько эффективных способов решения этой задачи.

Почему возникает ошибка мутабельности?

Платформа «1С» строго разграничивает контексты выполнения. Тип данных ДокументОбъект (как и ТаблицаЗначений, Граница, МоментВремени и др.) является мутабельным, то есть изменяемым. При пересечении границы «клиент-сервер» платформа пытается сериализовать параметры. Мутабельные типы не могут быть автоматически сериализованы и переданы по сети, поскольку их изменение на сервере не может быть синхронизировано с клиентом в реальном времени.


Решение 1. Возврат таблицы значений (для толстого клиента)

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

Разберем пример реализации этого механизма.

Создадим экспортную функцию в серверном общем модуле (например, ЗаполнениеСервер):


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

Теперь в клиентской обработке (в толстом клиенте) мы можем вызвать эту функцию и заполнить табличную часть нашего объекта:


Процедура ЗаполнитьТЧНаКлиенте()
    
    ПараметрыЗаполнения = Новый Структура("Родитель", ОбъектДокумента.ГруппаНоменклатуры);
    
    // Получаем ТЗ с сервера
    ТаблицаДанных = ЗаполнениеСервер.СформироватьТаблицуТоваров(ПараметрыЗаполнения);
    
    // Загружаем полученную таблицу прямо в ТЧ документа
    ОбъектДокумента.Товары.Загрузить(ТаблицаДанных);
    
КонецПроцедуры

Важное ограничение: Этот способ идеально работает в толстом клиенте. Однако, если ваша конфигурация будет развиваться в сторону тонкого клиента, данный код вызовет ошибку, так как ТаблицаЗначений также является мутабельным типом и не может быть передана на тонкий клиент.


Решение 2. Универсальный подход: Использование Массива Структур (Рекомендуется)

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

Проанализируем этот подход по шагам.

Шаг 1. Пишем серверную функцию получения данных:


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

Шаг 2. Вызываем функцию и заполняем документ на клиенте (или в любом другом месте):


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

Этот способ является стандартом разработки «1С» и гарантирует отсутствие ошибок независимо от режима запуска программы.


Решение 3. Сериализация в XML

На форуме также прозвучало предложение: «Попробуйте 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.Закрыть();
    
КонецФункции

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


Решение 4. Работа в контексте Управляемой формы

Если ваша задача решается в рамках управляемого приложения, а обработка заполнения вызывается из формы документа, то платформа сама предоставляет удобные механизмы конвертации типов.

На клиенте данные документа хранятся в типе ДанныеФормыСтруктура (переменная Объект). Мы можем передать управление на сервер контекстно, преобразовать данные формы в прикладной объект, выполнить заполнение серверным методом и вернуть изменения на форму:


&НаКлиенте
Процедура КомандаЗаполнить(Команда)
    ЗаполнитьДокументНаСервере();
КонецПроцедуры

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

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

Резюме

Для создания отказоустойчивой и производительной архитектуры при заполнении табличных частей придерживайтесь следующих правил:

  1. Отделяйте логику выборки данных (запросы) от логики непосредственного изменения объекта.
  2. Передавайте с сервера на клиент только немутабельные структуры данных (массивы, соответствия, структуры).
  3. Для универсального клиент-серверного взаимодействия используйте Массив Структур как наиболее легкий и надежный формат обмена.
← На главную