Как проверить существование объекта при загрузке данных через COM-соединение в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При организации обмена данными между различными базами данных 1С разработчики часто прибегают к технологии COM-соединения (COM Connector). Это мощный и относительно простой инструмент, позволяющий напрямую считывать данные из одной информационной базы (источника) и записывать их в другую (приемник). Однако на практике программисты часто сталкиваются с неочевидным поведением платформы при проверке существования загружаемых объектов.

В этой статье мы подробно разберем, почему стандартные проверки на Неопределено могут не работать, как правильно обрабатывать результаты поиска через COM, разберем типичные ошибки с контекстами баз и рассмотрим архитектурные подходы для создания надежного и быстрого обмена.

Разбор проблемы: типичный сценарий ошибки

Рассмотрим классическую ситуацию: нам необходимо перенести справочник Склады из базы-источника в базу-приемник. Логика проста — мы подключаемся по COM к базе-источнику, выбираем склады запросом, обходим выборку в цикле и пытаемся найти склад в текущей базе по наименованию. Если склад не найден — мы его создаем, если найден — перезаписывать или дублировать его не нужно.

Посмотрим на код, с которого часто начинают начинающие разработчики:


// ПРИМЕР НЕРАБОЧЕГО КОДА
Запрос = СоединениеБУХ.NewObject("Запрос");
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Склады.Наименование КАК Наименование,
    |   Склады.ТекущийОтветственный КАК ТекущийОтветственный
    |ИЗ
    |   Справочник.Склады КАК Склады";

РезультатЗапроса = Запрос.Выполнить.Выбрать();

Пока РезультатЗапроса.Следующий() Цикл
    // Попытка найти существующий склад в базе-приемнике
    Склад = Справочники.Склады.НайтиПоНаименованию(РезультатЗапроса.Наименование);
    
    Если Склад = Неопределено Тогда 
        НовыйСклад = Справочники.Склады.СоздатьЭлемент();
        НовыйСклад.Наименование = РезультатЗапроса.Наименование;
        НовыйСклад.Записать();
    КонецЕсли;
КонецЦикла;

При выполнении этого кода разработчики обнаруживают, что новые склады просто не создаются, даже если база-приемник абсолютно пуста. Выясним, почему это происходит и какие фундаментальные ошибки здесь допущены.

Ошибка 1. Непонимание возвращаемого значения метода НайтиПоНаименованию()

Первая и самая распространенная ошибка заключается в проверке условия Если Склад = Неопределено Тогда. Обратимся к синтакс-помощнику платформы 1С:Предприятие для метода НайтиПоНаименованию().

Данный метод возвращает значение типа СправочникСсылка или значение Неопределено. Однако важно знать условия, при которых возвращается то или иное значение:

Поскольку пустая ссылка не равна значению Неопределено, условие проверки в коде никогда не выполнялось, и новые элементы не создавались.

Правильное решение: для проверки на наличие ссылки следует использовать системную функцию ЗначениеЗаполнено() или проверять метод ссылки Пустая().


// Вариант 1 (рекомендуемый универсальный)
Если Не ЗначениеЗаполнено(Склад) Тогда
    // Объект не найден, создаем новый
КонецЕсли;

// Вариант 2 (через метод ссылки)
Если Склад.Пустая() Тогда
    // Объект не найден
КонецЕсли;

Ошибка 2. Путаница контекстов (Локальная база vs COM-соединение)

В процессе отладки кода обмена разработчики часто совершают еще одну критическую ошибку — выполняют поиск объекта не в той информационной базе. Посмотрим на эту строчку кода:


Склад = СоединениеБУХ.Справочники.Склады.НайтиПоНаименованию(РезультатЗапроса.Наименование);

Проанализируем эту ситуацию. Объект СоединениеБУХ — это внешнее COM-соединение с базой-источником. Считывая данные из источника и выполняя поиск через СоединениеБУХ.Справочники..., мы ищем склад в базе-источнике! Разумеется, он там всегда будет найден, ведь именно оттуда мы только что получили это наименование.

Для корректной синхронизации поиск должен выполняться строго в базе-приемнике (то есть локально, без использования префикса COM-соединения):


// Ищем в локальной базе-приемнике, где выполняется данный код
Склад = Справочники.Склады.НайтиПоНаименованию(РезультатЗапроса.Наименование);

Особенности проверки пустых ссылок при работе через COM

Если вам все же необходимо проверить существование объекта непосредственно на стороне COM-соединения, помните о специфике работы с COM-объектами. Передаваемые через COM-границу ссылки оборачиваются в тип COMОбъект. Напрямую сравнить такую ссылку с локальной пустой ссылкой или использовать локальную функцию ЗначениеЗаполнено() на стороне приемника не всегда безопасно, так как это может привести к неявным ошибкам приведения типов.

Если переменная Склад получена из COM-базы, проверка должна выглядеть следующим образом:


// Склад получен через СоединениеБУХ
Склад = СоединениеБУХ.Справочники.Склады.НайтиПоНаименованию(ИскомоеНаименование);

// Проверяем через вызов метода COM-объекта
Если Склад.Пустая() Тогда
    Сообщить("Склад во внешней базе не найден!");
КонецЕсли;

Также можно воспользоваться контекстом внешнего соединения для проверки заполненности значения:


Если Не СоединениеБУХ.ЗначениеЗаполнено(Склад) Тогда
    // Объект пустой на стороне COM-базы
КонецЕсли;

Улучшение архитектуры: Поиск по Уникальному Идентификатору (GUID)

Поиск элементов по наименованию (особенно таких объектов, как склады, контрагенты или номенклатура) — это крайне ненадежная практика. Пользователи в любой момент могут переименовать склад (например, исправить опечатку или сделать название «покрасивее»). При следующем запуске обмена система не найдет склад со старым наименованием и создаст дубликат.

Самым надежным способом синхронизации объектов между базами является использование их системных Уникальных Идентификаторов (GUID).

Давайте разберем, как передать GUID из базы-источника через COM и найти по нему объект в приемнике. Напрямую передать объект типа УникальныйИдентификатор через COM нельзя, но мы можем передать его строковое представление.

Рассмотрим по шагам правильный алгоритм синхронизации по GUID:

  1. В базе-источнике получаем строковое представление GUID склада.
  2. Передаем эту строку в базу-приемник.
  3. В приемнике инициализируем новый УникальныйИдентификатор из полученной строки.
  4. Выполняем поиск элемента в приемнике.

Посмотрим на практический пример реализации этого подхода:


// В запросе к COM-базе получаем строковый УИД ссылки
Запрос = СоединениеБУХ.NewObject("Запрос");
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Склады.Наименование КАК Наименование,
    |   ВЫРАЗИТЬ(Склады.Ссылка КАК Строка) КАК УИДСтрокой
    |ИЗ
    |   Справочник.Склады КАК Склады";

РезультатЗапроса = Запрос.Выполнить.Выбрать();

Пока РезультатЗапроса.Следующий() Цикл
    
    // Получаем строковый GUID из COM-соединения
    СтрокаGUID = СоединениеБУХ.String(РезультатЗапроса.Ссылка.УникальныйИдентификатор());
    
    // Создаем GUID в локальной базе
    ЛокальныйGUID = Новый УникальныйИдентификатор(СтрокаGUID);
    
    // Ищем склад по GUID в приемнике
    СкладСсылка = Справочники.Склады.ПолучитьСсылку(ЛокальныйGUID);
    
    // ВНИМАНИЕ: Метод ПолучитьСсылку() ВСЕГДА возвращает ссылку, 
    // даже если объекта физически нет в базе данных!
    // Для проверки реального существования нужно попытаться получить объект:
    
    СкладОбъект = СкладСсылка.ПолучитьОбъект();
    
    Если СкладОбъект = Неопределено Тогда
        // Объект реально отсутствует в базе приемнике. Создаем его с этим же GUID!
        НовыйСклад = Справочники.Склады.СоздатьЭлемент();
        НовыйСклад.УстановитьСсылкуНового(СкладСсылка);
        НовыйСклад.Наименование = РезультатЗапроса.Наименование;
        НовыйСклад.Записать();
        Сообщить("Создан новый склад: " + НовыйСклад.Наименование);
    Иначе
        // Склад существует. При необходимости можно обновить его реквизиты
        Если СкладОбъект.Наименование <> РезультатЗапроса.Наименование Тогда
            СкладОбъект.Наименование = РезультатЗапроса.Наименование;
            СкладОбъект.Записать();
            Сообщить("Обновлено наименование склада: " + СкладОбъект.Наименование);
        КонецЕсли;
    КонецЕсли;
    
КонецЦикла;

Профессиональное решение: Использование регистра соответствий

В случаях, когда базы данных имеют абсолютно разную структуру (например, самописная конфигурация и типовая «1С:Бухгалтерия»), или когда сопоставляемые объекты были созданы вручную в обеих базах и имеют разные GUID, лучшим решением будет использование вспомогательного регистра сведений «Соответствие объектов».

Создайте в базе-приемнике непериодический регистр сведений СоответствиеОбъектов со следующей структурой:

При таком подходе логика обмена становится максимально гибкой:


// Шаблон поиска сопоставления через регистр сведений
МенеджерЗаписи = РегистрыСведений.СоответствиеОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИДИсточника = СтрокаGUID_Источника;
МенеджерЗаписи.Прочитать();

Если МенеджерЗаписи.Выбран() Тогда
    // Объект уже сопоставлен, используем ссылку из ресурса
    ТекущийСклад = МенеджерЗаписи.СобственныйОбъект;
Иначе
    // Объект не сопоставлен. Ищем по наименованию или создаем новый
    ТекущийСклад = Справочники.Склады.НайтиПоНаименованию(НаименованиеИсточника);
    
    Если Не ЗначениеЗаполнено(ТекущийСклад) Тогда
        НовыйСклад = Справочники.Склады.СоздатьЭлемент();
        НовыйСклад.Наименование = НаименованиеИсточника;
        НовыйСклад.Записать();
        ТекущийСклад = НовыйСклад.Ссылка;
    КонецЕсли;
    
    // Записываем новое сопоставление в регистр, чтобы не искать в следующий раз
    НоваяЗапись = РегистрыСведений.СоответствиеОбъектов.СоздатьМенеджерЗаписи();
    НоваяЗапись.ИДИсточника = СтрокаGUID_Источника;
    НоваяЗапись.СобственныйОбъект = ТекущийСклад;
    НоваяЗапись.Записать();
КонецЕсли;

Использование регистра соответствий предотвратит дублирование информации, позволит пользователям безболезненно переименовывать объекты в любой из баз и сохранит прозрачную историю обмена данными.

← На главную