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