При интеграции различных информационных баз 1С между собой разработчики часто используют механизм COM-соединения. Этот способ позволяет напрямую обращаться к данным другой конфигурации, создавать, изменять и записывать объекты. Однако при работе в клиент-серверном варианте (особенно на СУБД SQL) программисты регулярно сталкиваются со специфическими ошибками платформы.
В данной статье мы подробно разберем, как правильно инициализировать COM-соединение, почему возникает ошибка «Попытка передачи с клиента на сервер мутабельного значения» при попытке записать созданный документ и какие существуют эффективные способы решения этой проблемы.
Проанализируем исходный код, с которого начал автор темы на форуме:
ПараметрыПодключения = "Srvr=""имясервера"";Ref=""имябазы"";Usr=""логин"";Pwd=""пароль"";";
ИмяКомОбъекта = "";
СистемнаяИнформация = Новый СистемнаяИнформация;
Если СтрНайти(СистемнаяИнформация.ВерсияПриложения, "8.3") > 0 Тогда
ИмяКомОбъекта = "V83.COMConnector";
ИначеЕсли СтрНайти(СистемнаяИнформация.ВерсияПриложения, "8.2") > 0 Тогда
ИмяКомОбъекта = "V82.COMConnector";
Иначе
ВызватьИсключение "Не удалось определить версию платформы. " + ОписаниеОшибки();
КонецЕсли;
Соединение = Новый COMОбъект(ИмяКомОбъекта);
НужныйДок = Соединение.Документы.РеализацияТоваровУслуг;
При выполнении этого кода система выдает ошибку: Поле объекта не обнаружено (Документы). Выясним причину этого поведения.
Дело в том, что объект V83.COMConnector сам по себе является лишь «менеджером» или «клиентом» для организации связи. Он не хранит внутри себя структуру метаданных целевой базы. Чтобы начать работать с объектами (справочниками, документами, регистрами) удаленной информационной базы, необходимо установить фактическое соединение с помощью метода Connect(), передав в него строку подключения.
Посмотрим на исправленный и правильный вариант инициализации подключения:
// Инициализируем коннектор
COMConnector = Новый COMОбъект(ИмяКомОбъекта);
// Устанавливаем непосредственное соединение с базой данных
Соединение = COMConnector.Connect(ПараметрыПодключения);
// Теперь объект "Соединение" содержит контекст удаленной базы,
// и мы можем обращаться к ее метаданным
НужныйДок = Соединение.Документы.РеализацияТоваровУслуг.СоздатьДокумент();
После вызова метода Connect() переменная Соединение возвращает указатель на контекст внешней базы данных. Теперь обращение к свойству Соединение.Документы отработает корректно.
Двигаемся дальше по шагам. Соединение успешно установлено, документ создан и заполнен. Однако при попытке вызвать метод Док.Записать() в целевой базе транзакция обрывается со следующей ошибкой:
Ошибка при вызове метода контекста (Записать). Произошла исключительная ситуация. Ошибки при выполнении обработки - ПриЗаписи. Попытка передачи с клиента на сервер мутабельного значения 1-го параметра.
Разберем природу этого явления. Термин «мутабельный» (от англ. mutable) означает изменчивый, динамический тип данных. К мутабельным объектам в системе 1С:Предприятие относятся тяжелые типы данных, состояние которых может меняться в процессе работы: например, ДокументОбъект, СправочникОбъект, ТаблицаЗначений, ДеревоЗначений, СписокЗначений.
В клиент-серверной архитектуре передача таких объектов напрямую между клиентским приложением и сервером запрещена платформой из соображений оптимизации трафика и целостности данных. При работе через COM-соединение выполняются следующие правила:
V83.COMConnector) классифицируется платформой как Внешнее соединение.Записать() в удаленной базе начинают отрабатывать стандартные механизмы: процедуры модуля объекта (ПередЗаписью, ПриЗаписи), а также назначенные на эти события Подписки на события.Источник или ЭтотОбъект), платформа пытается передать его из клиентского контекста внешнего соединения на сервер.Рассмотрим подробнее три основных способа обхода и устранения этой ошибки в зависимости от ваших задач и доступа к коду целевой базы.
Если ваша задача заключается в простом переносе готовых данных («как есть») и вам не требуется запускать сложные бизнес-цепочки, проверки заполнения, проведение документов или регистрацию изменений для других планов обмена на той стороне, наиболее простым решением будет использование режима загрузки.
Для этого перед вызовом метода Записать() необходимо установить свойство объекта ОбменДанными.Загрузка в значение Истина. Проанализируем пример кода:
Док = Соединение.Документы.РеализацияТоваровУслуг.СоздатьДокумент();
Док.Номер = Объект.Документ.Номер;
Док.Дата = Объект.Документ.Дата;
Док.Организация = Соединение.Справочники.Организации.НайтиПоКоду("000000001");
// Включаем режим загрузки данных, чтобы отключить стандартные обработчики и подписки
Док.ОбменДанными.Загрузка = Истина;
Док.Записать();
Преимущества: Данный способ не требует внесения изменений в конфигурацию целевой базы. Документ гарантированно запишется, так как большинство тяжелых серверных подписок на события и проверок при записи просто проигнорируются платформой.
Недостатки: Не выполняются типовые проверки конфигурации, не заполняются связанные регистры (если это было завязано на обработчики событий), документ записывается в «сыром» виде.
Если бизнес-логика требует обязательного выполнения всех проверок и обработчиков при записи документа через COM-соединение, нам придется проанализировать конфигурацию целевой базы.
Выясним причину ошибки в конфигураторе целевой базы:
Если ошибка возникает в вашей собственной подписке на событие или в кастомном общем модуле целевой конфигурации, правильным архитектурным решением будет рефакторинг этого кода. Проанализируем, как избежать передачи мутабельного объекта.
Плохой вариант (вызывает ошибку при COM-соединении):
// Код в подписке на событие "ПриЗаписи"
Процедура ПриЗаписиДокумента(Источник, Отказ)
// Передаем весь объект "Источник" (мутабельное значение) на сервер
РаботаСДокументамиСервер.ОбработатьДокумент(Источник);
КонецПроцедуры
Хороший вариант (исправленный код):
Вместо передачи тяжелого объекта целиком, передавайте его уникальную ссылку (тип ДокументСсылка не является мутабельным и отлично передается между контекстами клиента, сервера и внешнего соединения):
Процедура ПриЗаписиДокумента(Источник, Отказ)
// Передаем ссылку на документ, которая легко сериализуется
РаботаСДокументамиСервер.ОбработатьДокументПоСсылке(Источник.Ссылка);
КонецПроцедуры
Если же документ еще ни разу не был записан и у него нет ссылки (например, в событии ПередЗаписью), то вместо передачи самого объекта соберите все необходимые для анализа реквизиты в структуру (тип Структура, состоящий из примитивных типов, успешно передается между клиентом и сервером):
Процедура ПередЗаписьюДокумента(Источник, Отказ)
ПараметрыДокумента = Новый Структура;
ПараметрыДокумента.Вставить("Дата", Источник.Дата);
ПараметрыДокумента.Вставить("Организация", Источник.Организация);
// Передаем легкую структуру вместо тяжелого объекта
РаботаСДокументамиСервер.ПроверитьДанные(ПараметрыДокумента);
КонецПроцедуры
Мы детально разобрали основные сложности, возникающие при создании и записи документов в 1С через COM-соединение. Помните два золотых правила:
Connector.Connect(ПараметрыПодключения) для инициализации сеанса связи, прежде чем обращаться к метаданным.ОбменДанными.Загрузка = Истина, чтобы избежать конфликтов с серверными подписками на события и обойти ошибку передачи мутабельных значений. Если же обработка бизнес-логики необходима, убедитесь в наличии флага «Внешнее соединение» у задействованных общих модулей конфигурации-приемника.