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