Как вернуть ссылку на созданный контрагент при COM-обмене в 1С

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

При реализации интеграционных механизмов и обмена данными между конфигурациями 1С через COM-соединение начинающие разработчики часто сталкиваются с проблемой «исчезающих» или пустых ссылок. Типичный сценарий: данные успешно переносятся, новые элементы в базе-приемнике создаются, но в реквизитах создаваемых документов они не отображаются. Однако при повторном запуске обмена, когда элементы уже существуют в базе, ссылки волшебным образом заполняются.

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

Анализ проблемы: почему ссылка не появляется с первого раза

Рассмотрим ситуацию на примере загрузки документа ОтчетКомиссионераОПродажах. В коде заполнения документа реквизит Контрагент заполняется результатом работы функции НайтиСоздатьКонтрагенты():


Документ = Документы.ОтчетКомиссионераОПродажах.СоздатьДокумент;
Документ.Контрагент = НайтиСоздатьКонтрагенты(стр);
Документ.Организация = стр.Организация;
Документ.Дата = стр.Дата;
Документ.Номер = стр.Номер;

СтрокаПокупатели = Документ.Покупатели.Добавить;
СтрокаПокупатели.Покупатель = НайтиСоздатьКонтрагенты(стр);

СтрокаУслуги = Документ.Услуги.Добавить;
СтрокаУслуги.Номенклатура = стр.Номенклатура;
СтрокаУслуги.Количество = стр.Количество;
СтрокаУслуги.Цена = стр.Цена;
СтрокаУслуги.Сумма = стр.Сумма;
Документ.СуммаДокумента = стр.Сумма;

Документ.Записать(РежимЗаписиДокумента.Проведение);

Проанализируем исходный вариант функции НайтиСоздатьКонтрагенты(), в котором и крылась основная ошибка:


Функция НайтиСоздатьКонтрагенты(стр)
    Найти = Справочники.Contragenty.НайтиПоНаименованию(стр.Контрагент);
    Если Найти = Справочники.Контрагенты.ПустаяСсылка() Тогда
        НовыйОбъект = Справочники.Контрагенты.СоздатьЭлемент();
        НовыйОбъект.Наименование = стр.Contragent;
        НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ФизЛицо;
        НовыйОбъект.Родитель = Справочники.Контрагенты.НайтиПоНаименованию("Головные агенты");
        НовыйОбъект.Записать();
    КонецЕсли;
    Возврат Найти;
КонецФункции

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

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

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

Для исправления этой ошибки необходимо в блоке создания нового элемента явно присвоить переменной поиска ссылку на только что записанный объект. Метод Записать() инициализирует ссылку нового объекта БД, и мы можем получить её через свойство Ссылка.

Разберем исправленный и оптимизированный вариант функции:


Функция НайтиСоздатьКонтрагенты(стр)
    Найти = Справочники.Контрагенты.НайтиПоНаименованию(стр.Контрагент);
    
    Если Найти = Справочники.Контрагенты.ПустаяСсылка() Тогда
        НовыйОбъект = Справочники.Контрагенты.СоздатьЭлемент();
        НовыйОбъект.Наименование = стр.Контрагент;
        НовыйОбъект.ЮрФизЛицо = Перечисления.ЮрФизЛицо.ФизЛицо;
        НовыйОбъект.Родитель = Справочники.Контрагенты.НайтиПоНаименованию("Головные агенты");
        НовыйОбъект.Записать();
        
        // Ключевое исправление: получаем ссылку нового объекта
        Найти = НовыйОбъект.Ссылка;
    КонецЕсли;
    
    Возврат Найти;
КонецФункции

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

Пять важных архитектурных правил для COM-обменов

Решение локальной ошибки с пустой ссылкой устраняет явный сбой, но для построения надежной промышленной интеграции необходимо учитывать особенности работы платформы 1С. Ниже приведены рекомендации, которые уберегут вашу базу данных от логических ошибок и критического снижения производительности.

  1. Синхронизация по уникальному идентификатору (GUID) вместо наименования.

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

    При создании нового объекта в приемнике используйте метод УстановитьСсылкуНового(). Это позволит гарантировать идентичность GUID в обеих базах:

    
    // Пример генерации ссылки по переданному GUID-строке
    УникальныйИдентификатор = Новый УникальныйИдентификатор(СтрокаGUIDИзИсточника);
    НоваяСсылка = Справочники.Контрагенты.ПолучитьСсылку(УникальныйИдентификатор);
    
    НовыйОбъект = Справочники.Контрагенты.СоздатьЭлемент();
    НовыйОбъект.УстановитьСсылкуНового(НоваяСсылка);
    // Заполнение реквизитов...
    НовыйОбъект.Записать();
    
  2. Использование транзакций для обеспечения целостности данных.

    В представленном выше коде контрагент записывается сразу, а документ — в самом конце. Если при проведении документа возникнет ошибка (например, из-за блокировки периода или отсутствия остатков), документ записан не будет. Но новый контрагент при этом уже создан и останется в базе «осиротевшим». Это приводит к засорению базы данных.

    Оборачивайте процесс создания связанных объектов в транзакцию:

    
    НачатьТранзакцию();
    Попытка
        // Создаем/находим контрагента
        СсылкаКонтрагент = НайтиСоздатьКонтрагенты(стр);
        
        // Создаем и записываем документ
        ДокументОбъект = Документы.ОтчетКомиссионераОПродажах.СоздатьДокумент();
        // Заполнение документа...
        ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        ВызватьИсключение "Не удалось записать пакет данных: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
    КонецПопытки;
    
  3. Безопасный поиск родительских групп справочника.

    Строка НовыйОбъект.Родитель = Справочники.Контрагенты.НайтиПоНаименованию("Головные агенты"); таит в себе опасность. Если пользователь случайно удалит, переименует или переместит папку «Головные агенты», метод вернет пустую ссылку, и новые контрагенты начнут без предупреждения создаваться в корне справочника. Если папок с таким именем окажется две, метод вернет первую попавшуюся. Безопаснее использовать предопределенные папки, либо искать группу по её фиксированному GUID, а при ее отсутствии — программно создавать ее с флагом ЭтоГруппа = Истина.

  4. Оптимизация производительности COM-соединения.

    Построчный вызов функции создания/поиска объектов для каждой строки документа при больших объемах обмена будет работать чрезвычайно медленно. Каждый вызов метода через COM-соединение несет накладные расходы на маршалинг данных между процессами.

    Гораздо эффективнее предварительно собрать все идентификаторы/имена контрагентов из пакета загружаемых данных в массив, передать этот массив в базу-приемник одним запросом, получить таблицу соответствий «Имя/GUID — Ссылка» и кэшировать её в памяти на время сессии обмена. Это ускорит импорт данных в десятки раз.

  5. Правильный выбор технологии подключения: COMConnector против Application.

    Для фонового и программного обмена данными всегда используйте компонент V83.COMConnector, а не V83.Application. Первое решение работает «тихо» (в фоновом режиме без запуска графического интерфейса), потребляет значительно меньше серверных ресурсов и гарантирует корректное завершение процесса при закрытии соединения. Application запускает полноценное клиентское приложение, что сильно снижает производительность и часто приводит к зависанию процессов в операционной системе Windows.

← На главную