При организации интеграции между конфигурациями 1С с помощью внешнего соединения через COM-объект разработчики нередко сталкиваются с ошибкой времени выполнения:
{Форма.Форма.Форма(446)}: Значение не является значением объектного типа (ОбменДанными)
Ошибка проявляется в момент попытки включить режим загрузки данных у полученного объекта:
Договор_БУХ_Объект.ОбменДанными.Загрузка = Истина;
Давайте подробно разберем, почему возникает данное исключение, какие особенности платформы 1С приводят к неожиданному поведению кода и как правильно реализовать обновление или создание объектов через COM-соединение.
Рассмотрим фрагмент кода, в котором возникает ошибка:
УИД_БУХ = Бухгалтерия.NewObject("УникальныйИдентификатор", Строка(Договор.УникальныйИдентификатор()));
Договор_БУХ = Бухгалтерия.Справочники.ДоговорыКонтрагентов.ПолучитьСсылку(УИД_БУХ);
Если НЕ Договор_БУХ.Пустая() Тогда
// Попытка получить объект
Договор_БУХ_Объект = Договор_БУХ.ПолучитьОбъект();
Договор_БУХ_Объект.ОбменДанными.Загрузка = Истина; // Ошибка здесь!
// ...
КонецЕсли;
На первый взгляд кажется, что условие Если НЕ Договор_БУХ.Пустая() Тогда защищает от обращения к несуществующим данным. Однако здесь кроется фундаментальная особенность платформы 1С:
ПолучитьСсылку() не обращается к базе данных. Он конструирует ссылку на основании переданного УникальныйИдентификатор исключительно в оперативной памяти. Платформа формирует ссылку с заданным GUID вне зависимости от того, записан ли такой элемент в таблице информационной базы.Пустая() всегда возвращает Ложь. Поскольку переданный GUID содержит конкретное значение (не является нулевым идентификатором 00000000-0000-0000-0000-000000000000), ссылка не считается пустой. Поэтому проверка НЕ Договор_БУХ.Пустая() всегда будет истинной, даже если объекта в базе не существует.Договор_БУХ.Реквизит1, платформа вернет значение по умолчанию для соответствующего типа (пустую строку, 0 или пустую ссылку), создавая иллюзию корректной работы.ПолучитьОбъект() возвращает Неопределено. Когда платформа пытается вычитать объект из базы по несуществующей ссылке, физическая запись не находится, и метод возвращает значение типа Неопределено. В результате следующая строка кода пытается обратиться к свойству ОбменДанными у значения Неопределено, что и вызывает ошибку «Значение не является значением объектного типа».Кроме того, ПолучитьОбъект() может возвращать Неопределено даже при наличии физической записи в базе, если у пользователя внешнего COM-соединения действуют ограничения прав на уровне записей (RLS) или отсутствуют права на чтение/изменение данного справочника.
Разберем самый прямой способ исправления ошибки: проверка переменной объекта перед обращением к ее свойствам и методам. Проанализируем корректный код:
УИД_БУХ = Бухгалтерия.NewObject("УникальныйИдентификатор", Строка(Договор.УникальныйИдентификатор()));
Договор_БУХ = Бухгалтерия.Справочники.ДоговорыКонтрагентов.ПолучитьСсылку(УИД_БУХ);
Договор_БУХ_Объект = Договор_БУХ.ПолучитьОбъект();
Если Договор_БУХ_Объект <> Неопределено Тогда
Договор_БУХ_Объект.ОбменДанными.Загрузка = Истина;
Договор_БУХ_Объект.Реквизит1 = Договор.Реквизит1;
Договор_БУХ_Объект.Записать();
Иначе
// Элемент с таким УИД в базе-приемнике не найден
// Здесь можно выполнить создание нового элемента
КонецЕсли;
Такой подход полностью устраняет падение программы, однако требует внимательности, если логика обмена предполагает создание нового элемента при отсутствии старого.
Если цель обмена состоит в том, чтобы обновить объект при наличии либо создать его с тем же уникальным идентификатором при отсутствии, разберем паттерн с методом УстановитьСсылкуНового():
УИД_БУХ = Бухгалтерия.NewObject("УникальныйИдентификатор", Строка(Договор.УникальныйИдентификатор()));
Договор_БУХ = Бухгалтерия.Справочники.ДоговорыКонтрагентов.ПолучитьСсылку(УИД_БУХ);
ОбъектБУХ = Договор_БУХ.ПолучитьОбъект();
Если ОбъектБУХ = Неопределено Тогда
// Создаем новый элемент, если объекта в базе еще нет
ОбъектБУХ = Бухгалтерия.Справочники.ДоговорыКонтрагентов.СоздатьЭлемент();
ОбъектБУХ.УстановитьСсылкуНового(Договор_БУХ);
КонецЕсли;
// Теперь ОбъектБУХ гарантированно содержит объектную сущность
ОбъектБУХ.ОбменДанными.Загрузка = Истина;
ОбъектБУХ.Наименование = Договор.Наименование;
ОбъектБУХ.Реквизит1 = Договор.Реквизит1;
ОбъектБУХ.Записать();
Метод УстановитьСсылкуНового() назначает новому объекту заранее подготовленную ссылку, обеспечивая синхронизацию идентификаторов между базами данных.
Вызов ПолучитьОбъект() считывает из базы всю структуру данных (включая все табличные части) и может захватывать управляемые блокировки. Если требуется только проверить, существует ли объект, быстрее и экономнее выполнить запрос во внешней базе:
ЗапросБУХ = Бухгалтерия.NewObject("Запрос");
ЗапросБУХ.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Договоры.Ссылка КАК Ссылка
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Договоры
|ГДЕ
| Договоры.Ссылка = &Ссылка";
ЗапросБУХ.УстановитьПараметр("Ссылка", Договор_БУХ);
Выборка = ЗапросБУХ.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Договор_БУХ_Объект = Договор_БУХ.ПолучитьОбъект();
Договор_БУХ_Объект.ОбменДанными.Загрузка = Истина;
Договор_БУХ_Объект.Реквизит1 = Договор.Реквизит1;
Договор_БУХ_Объект.Записать();
Иначе
// Действия при отсутствии записи в базе
КонецЕсли;
ОбменДанными.Загрузка = Истина: оно отключает стандартные проверки в обработчиках ПередЗаписью и ПриЗаписи (при условии, что в модулях конфигурации прописано стандартное условие Если ОбменДанными.Загрузка Тогда Возврат; КонецЕсли;). Однако это свойство не отменяет ограничения базы данных на уровне СУБД (например, уникальность предопределенных полей и контроль типов данных).Попытка ... Исключение, чтобы обрыв сетевого соединения или сбой записи не оставили базу в частично несогласованном состоянии:
Бухгалтерия.НачатьТранзакцию();
Попытка
Договор_БУХ_Объект.Записать();
Бухгалтерия.ЗафиксироватьТранзакцию();
Исключение
Бухгалтерия.ОтменитьТранзакцию();
ВызватьИсключение "Ошибка при записи через COM: " + ОписаниеОшибки();
КонецПопытки;