При работе в режиме обычного приложения (Толстый клиент) разработчики часто сталкиваются с неприятной ситуацией: подписка на событие (например, ПередЗаписью) срабатывает на стороне клиента, и при попытке передать Источник в серверный модуль платформа выдает ошибку «Передача мутабельного значения». Проанализируем, почему это происходит и какими способами можно обойти это ограничение платформы 1С:Предприятие 8.
Для начала разберем природу возникновения проблемы. В 1С существуют понятия «сериализуемых» и «мутабельных» (изменяемых) типов данных. Мутабельные типы, такие как СправочникОбъект, ДокументОбъект или ТаблицаЗначений, не могут быть переданы между контекстами клиента и сервера «как есть», так как они представляют собой сложные ссылки на области памяти, которые актуальны только там, где объект был создан.
В обычном приложении (Толстый клиент), если пользователь открыл форму и нажал кнопку «Записать», создание и инициализация объекта происходят непосредственно в клиентском сеансе. Следовательно, и обработчики событий, включая подписки на события, вызываются в контексте клиента. Когда код внутри подписки пытается вызвать процедуру общего модуля, у которого стоит флаг Сервер, платформа пытается отправить Источник (наш объект) на сервер. Поскольку объект мутабелен, попытка сериализации заканчивается ошибкой.
Рассмотрим наиболее правильный с точки зрения архитектуры способ. Вместо того чтобы передавать на сервер весь тяжелый объект, мы можем извлечь из него только те данные, которые необходимы для выполнения логики в привилегированном режиме. Для этого используются типы Структура, Массив или Соответствие.
Проанализируем пример кода в подписке на стороне клиента:
Процедура ОбменДаннымиПередЗаписью(Источник, Отказ) Экспорт
// Подписка выполняется на клиенте в толстом клиенте
// Создаем структуру с необходимыми данными
ПараметрыОбъекта = Новый Структура;
ПараметрыОбъекта.Вставить("Ссылка", Источник.Ссылка);
ПараметрыОбъекта.Вставить("Номер", Источник.Номер);
ПараметрыОбъекта.Вставить("ПометкаУдаления", Источник.ПометкаУдаления);
// Передаем структуру на сервер (это разрешено)
МойСерверныйМодуль.ВыполнитьЛогикуНаСервере(ПараметрыОбъекта);
КонецПроцедуры
В этом случае на сервере мы можем получить объект заново по ссылке через Источник.Ссылка.ПолучитьОбъект(), если нам нужно внести в него изменения, или просто использовать данные из структуры для проведения проверок.
Если нам необходимо передать информацию «сквозь» уровни вызовов, не меняя сигнатуры функций, разберем использование системного свойства ДополнительныеСвойства. Это свойство есть практически у всех ссылочных типов и представляет собой Структуру.
Посмотрим, как это работает:
Источник.ДополнительныеСвойства.ПередЗаписью или ПриЗаписи), который уже может выполняться на сервере, мы считываем эти данные.Важно: Помните, что ДополнительныеСвойства также должны содержать только сериализуемые значения, если предполагается переход контекста.
Проанализируем ситуацию с флагами компиляции. Если мы установим у общего модуля, где находится код подписки, одновременно флаги Клиент (обычное приложение) и Сервер, то в толстом клиенте приоритет будет отдан клиентской компиляции. Это означает, что код будет считаться «клиентским».
Чтобы избежать ошибки мутабельности без передачи параметров, можно попробовать перенести логику в модуль, где доступен серверный контекст, но вызвать его правильно. Однако следует помнить правило: где живет объект, там и выполняется подписка. Если объект на клиенте — подписка на клиенте. Исправить «мозги» платформе и заставить её перебросить мутабельный объект на сервер автоматически невозможно.
Часто разработчики пытаются использовать УстановитьПривилегированныйРежим(Истина) прямо в клиентской подписке. Выясним, почему это не работает. Метод УстановитьПривилегированныйРежим имеет доступность: Сервер, толстый клиент (только в файловом варианте), внешнее соединение. В клиент-серверном варианте вызов этого метода на стороне клиента просто игнорируется платформой. Права не повышаются.
Для корректной работы с привилегированным режимом нам в любом случае необходимо попасть на сервер. Рассмотрим шаги:
Структуру.УстановитьПривилегированныйРежим(Истина).Если объект еще не записан в базу (новая ссылка), а данных в нем слишком много, чтобы вручную перекладывать их в структуру, можно прибегнуть к сериализации в XML. Это позволяет превратить весь объект в строку, передать её на сервер и там десериализовать обратно в объект.
Разберем примерную схему:
// На клиенте
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписатьXML(ЗаписьXML, Источник);
СтрокаXML = ЗаписьXML.Закрыть();
// Передаем СтрокаXML на сервер
МойСерверныйМодуль.ОбработатьОбъектИзXML(СтрокаXML);
На сервере мы выполняем обратное действие через ЧтениеXML и ПрочитатьXML. Это «честный» способ передать текущее состояние объекта, включая все табличные части, без возникновения ошибки мутабельности.
Подводя итог, при работе с подписками в обычном приложении мы рекомендуем придерживаться следующей последовательности действий:
Источник.Ссылка) и вспомогательные параметры через Структуру.Вызов сервера необходим для перехода из клиентской подписки в серверный код.УстановитьПривилегированныйРежим в файловой и клиент-серверной базах на толстом клиенте различается, что может маскировать ошибки при разработке.Таким образом, проблема мутабельности — это не «гадость» платформы, а архитектурное ограничение, призванное защитить целостность данных при передаче между физическими узлами системы (клиентским компьютером и сервером приложений).