Каждый разработчик на платформе «1С:Предприятие», особенно при переходе к управляемому приложению и клиент-серверной архитектуре, неизбежно сталкивается с грозным предупреждением системы: «Попытка передачи с клиента на сервер мутабельного значения». Эта ошибка способна поставить в тупик начинающих программистов. Разберем подробно, что скрывается за загадочным словом «мутабельность», почему платформа накладывает жесткие ограничения на передачу таких данных и как правильно писать код, чтобы навсегда избежать этой проблемы.
Для начала обратимся к теории компьютерных наук. Термин мутабельный происходит от английского слова mutable (изменяемый). Соответственно, противопоставлением ему является иммутабельный (immutable — неизменяемый).
В программировании эти понятия определяют поведение объекта в оперативной памяти после его создания:
СправочникСсылка.<Имя> или ДокументСсылка.<Имя>). Ссылка — это просто уникальный идентификатор (GUID), его нельзя «изменить» частично; можно только заменить одну ссылку на другую, создав новую переменную.ТаблицаЗначений, Структура, Массив, а также прикладные объекты баз данных (СправочникОбъект.<Имя>, ДокументОбъект.<Имя>). Вы можете добавлять строки в таблицу значений или менять реквизиты документа, но сам объект в памяти остается тем же самым.Главное академическое различие между ними кроется в механизме сравнения. Иммутабельные типы всегда сравниваются «по значению», а мутабельные — «по ссылке в памяти» (идентичности). Проанализируем простой пример кода:
// Сравнение иммутабельных типов (строки)
Строка1 = "Привет";
Строка2 = "Привет";
// Результат сравнения будет Истина, так как значения совпадают
Если Строка1 = Строка2 Тогда
Сообщить("Строки равны!");
КонецЕсли;
// Сравнение мутабельных типов (таблицы значений)
ТЗ1 = Новый ТаблицаЗначений;
ТЗ2 = Новый ТаблицаЗначений;
// Результат сравнения будет Ложь, несмотря на то, что обе таблицы абсолютно пустые и одинаковые.
// Они расположены по разным адресам в памяти.
Если ТЗ1 = ТЗ2 Тогда
Сообщить("Таблицы равны!");
Иначе
Сообщить("Таблицы НЕ равны!");
КонецЕсли;
Казалось бы, если мутабельность — это просто естественное свойство сложных объектов изменяться в памяти, почему тогда платформа 1С запрещает передавать их при вызове серверных процедур с клиента? На форумах часто пишут, что причина в невозможности сериализации (перевода объекта в XML или JSON). Однако это миф: ДокументОбъект или ТаблицаЗначений прекрасно сериализуются платформой во внутренние XML-форматы.
Истинная причина запрета передачи мутабельных значений — отсутствие автоматической синхронизации памяти между клиентом и сервером.
Когда вы вызываете серверный метод из клиентского контекста (например, из модуля управляемой формы), выполнение программы физически переносится с компьютера пользователя на сервер приложений (процесс rphost). Между ними лежит сетевой канал.
Если бы платформа разрешила передать на сервер мутабельный объект (например, ДокументОбъект.ПриходнаяНакладная), то произошло бы следующее:
Чтобы защитить разработчика от подобных коллизий, архитекторы платформы 1С приняли жесткое решение: полностью запретить прямую передачу потенциально опасных мутабельных объектов через границу «клиент-сервер».
В тексте ошибки 1С использует слово «мутабельный» как синоним для объектов, которые запрещено передавать между контекстами выполнения. Проанализируем, какие типы входят в этот «черный список»:
СправочникОбъект.<Имя>, ДокументОбъект.<Имя>, ПланВидовХарактеристикОбъект.<Имя> и все остальные типы с суффиксом «Объект». Также сюда относятся наборы записей регистров (например, РегистрСведенийНаборЗаписей.<Имя>).ТабличныйДокумент, ТекстовыйДокумент, ПостроительОтчета, ДиаграммаГанта.ТаблицаЗначений, ДеревоЗначений (они вообще не доступны в контексте тонкого клиента).СтрокаТаблицыЗначений. Пытаясь передать одну строку таблицы, вы неявно пытаетесь передать и саму таблицу, что вызывает ошибку.Важное исключение: Такие универсальные коллекции, как Массив, Структура и Соответствие, технически являются изменяемыми (мутабельными). Однако платформа разрешает передавать их между клиентом и сервером. Почему? Потому что они являются «легковесными» контейнерами данных, не привязанными к транзакционной логике базы данных. Платформа просто копирует их значения при передаче (передает по значению через сериализацию).
Выяснив причины ограничений, рассмотрим подробнее рабочие паттерны проектирования, которые позволяют безопасно и эффективно передавать информацию между клиентом и сервером без возникновения ошибок.
Если вам нужно выполнить какие-то действия с документом или справочником на сервере, никогда не передавайте сам объект, полученный на клиенте. Передавайте его ссылку.
Рассмотрим неправильный вариант кода:
// НЕПРАВИЛЬНО! Вызовет ошибку мутабельности на клиенте
&НаКлиенте
Процедура ОбработатьДокумент(ДокументОбъект)
ВыполнитьОбработкуНаСервере(ДокументОбъект);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ВыполнитьОбработкуНаСервере(ДокументОбъект)
// Какой-то серверный код
КонецПроцедуры
А теперь разберем по шагам, как сделать это правильно с получением объекта непосредственно в серверном контексте:
// ПРАВИЛЬНО! Передаем иммутабельную ссылку
&НаКлиенте
Процедура ОбработатьДокумент(ДокументСсылка)
ВыполнитьОбработкуНаСервере(ДокументСсылка);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ВыполнитьОбработкуНаСервере(ДокументСсылка)
// Получаем объект из базы данных непосредственно на сервере
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
// Вносим изменения и записываем
ДокументОбъект.Комментарий = "Обработано автоматически";
ДокументОбъект.Записать();
КонецПроцедуры
В управляемом интерфейсе 1С предлагает великолепный механизм автоматической синхронизации данных — семейство типов ДанныеФормы... (например, ДанныеФормыКоллекция, которая на форме заменяет ТаблицуЗначений).
Эти типы специально спроектированы так, чтобы разработчик мог работать с ними как на клиенте, так и на сервере. Сама платформа берет на себя заботу о трансляции изменений. Если у вас на форме есть таблица, связанная с реквизитом формы типа ТаблицаЗначений, передавать ее в процедуры с контекстом формы (&НаСервере) вообще не нужно — она уже доступна на сервере через контекст (ЭтотОбъект или напрямую по имени реквизита).
Если же вы хотите использовать процедуру без контекста (&НаСервереБезКонтекста), передавайте реквизит формы напрямую:
&НаКлиенте
Процедура ИзменитьТаблицуФормы()
// РеквизитФормыТаблица имеет тип ДанныеФормыКоллекция
ОбработатьТаблицуНаСервере(Объект.Товары);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ОбработатьТаблицуНаСервере(ДанныеТаблицы)
// ДанныеФормыКоллекция без проблем передаются на сервер
Для Каждого СтрокаТЧ Из ДанныеТаблицы Цикл
СтрокаТЧ.Количество = СтрокаТЧ.Количество + 1;
КонецЦикла;
КонецПроцедуры
Что делать, если на клиенте вы динамически создали объект типа ТаблицаЗначений (хотя в тонком клиенте это невозможно без костылей, но часто встречается в толстом клиенте или при внешних соединениях) и вам нужно передать эти данные на сервер?
Лучший способ — свернуть данные в массив структур. Массив и структуры разрешены к передаче, так как они легко сериализуются платформой.
Посмотрим на пример функции конвертации:
// Пример преобразования Таблицы Значений в Массив Структур для передачи на сервер
Функция ПолучитьТаблицуДляПередачи(ТаблицаИсточника) Экспорт
МассивРезультат = Новый Массив;
// Получаем список колонок
МассивКолонок = Новый Массив;
Для Каждого Колонка Из ТаблицаИсточника.Колонки Цикл
МассивКолонок.Добавить(Колонка.Имя);
КонецЦикла;
// Заполняем массив структурами
Для Каждого Строка Из ТаблицаИсточника Цикл
НоваяСтруктура = Новый Структура;
Для Каждого ИмяКолонки Из МассивКолонок Цикл
НоваяСтруктура.Вставить(ИмяКолонки, Строка[ИмяКолонки]);
КонецЦикла;
МассивРезультат.Добавить(НоваяСтруктура);
КонецЦикла;
Возврат МассивРезультат;
КонецФункции
Полученный массив структур передается на сервер без каких-либо ошибок, а на сервере из него при необходимости можно за секунды воссоздать полноценную ТаблицуЗначений.
Если вам жизненно необходимо передать с клиента на сервер сложный, тяжелый или неподдерживаемый объект, который умеет сериализоваться (например, настройки отчета), воспользуйтесь механизмом Временного хранилища.
Принцип работы выглядит следующим образом:
ПоместитьВоВременноеХранилище().ПолучитьИзВременногоХранилища().Проанализируем пример кода:
&НаКлиенте
Процедура ПередатьСложныйОбъект(МойМутабельныйОбъект)
// Помещаем объект в хранилище на клиенте
АдресХранилища = ПоместитьВоВременноеХранилище(МойМутабельныйОбъект, УникальныйИдентификатор);
// Передаем только строку-адрес
ПринятьОбъектНаСервере(АдресХранилища);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ПринятьОбъектНаСервере(Знач АдресХранилища)
// Извлекаем чистый объект на сервере
ВосстановленныйОбъект = ПолучитьИзВременногоХранилища(АдресХранилища);
// Теперь мы можем работать с объектом на стороне сервера
// ...
КонецПроцедуры
Понимание природы мутабельности объектов — это важная ступень профессионального роста разработчика 1С. Помните следующие правила: