Как передавать объекты между базами 1С через COM-соединение, чтобы избежать ошибки "Объект не найден"?

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

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

В чем причина ошибки "Объект не найден"?

Ошибка «Объект не найден» при обмене данными через COM-соединение между базами 1С возникает по нескольким ключевым причинам, которые мы сейчас рассмотрим. Понимание этих причин поможет нам выбрать правильный подход к решению проблемы.

  1. Объекты как временные структуры памяти: Прежде всего, важно понимать, что объект 1С, с которым мы работаем в коде (например, элемент справочника Контрагенты), является временной структурой в оперативной памяти конкретного рабочего процесса. Этот объект представляет собой прочитанную запись из базы данных, с которой мы можем производить различные операции. Однако, сам по себе этот «живой» объект из одной базы не может быть просто «передан» в другую базу, поскольку он привязан к контексту своей информационной базы.
  2. Разные УИДы объектов метаданных при копировании конфигурации: Часто мы создаем новую базу не полной выгрузкой/загрузкой информационной базы (файла .dt), а только лишь выгрузкой/загрузкой конфигурации. Когда мы копируем конфигурацию или создаем дубль базы таким способом, платформа 1С может генерировать новые уникальные идентификаторы (УИДы) для объектов метаданных (например, для самого справочника «Контрагенты», для его реквизитов и т.д.). Даже если логическая структура баз кажется идентичной, внутренние идентификаторы, используемые платформой, могут различаться. Это критически важно, так как ссылки на объекты в 1С (которые сами по себе содержат УИДы объектов данных) косвенно зависят от УИДов метаданных.
  3. Неподходящие функции сериализации для кросс-базового обмена: Мы часто пытаемся использовать функции, предназначенные для внутренних операций, для обмена между базами. Одна из таких функций – ЗначениеВСтрокуВнутр(). Эта функция сериализует значение объекта в строку, включая его внутренние идентификаторы типа данных и метаданных. Проблема в том, что эти внутренние идентификаторы специфичны для конкретной информационной базы. Когда мы пытаемся восстановить такое значение с помощью ЗначениеИзСтрокиВнутр() в другой базе, где эти внутренние идентификаторы могут быть иными, платформа не может найти соответствующий тип или метаданный, и мы получаем ошибку «Объект не найден».

    Платформа 1С:Предприятие 8 не рекомендует использовать ЗначениеВСтрокуВнутр() и ЗначениеИзСтрокиВнутр() для обмена между различными информационными базами, именно из-за их привязки к внутренним идентификаторам метаданных конкретной ИБ. Эти функции предназначены для сохранения и восстановления значений внутри одной и той же информационной базы (например, для сохранения параметров отчета в пользовательских настройках).

  4. Несоответствие версий платформы и сборок COM-компоненты: Хотя это не напрямую связано с проблемой УИДов, несоответствие версий платформы или разрядности COM-компоненты (32-бит против 64-бит) между клиентским приложением, инициализирующим COM-соединение, и серверной частью 1С, к которой мы подключаемся, также может приводить к ошибкам типа «Метод объекта не обнаружен» или невозможности установить соединение, что косвенно блокирует обмен данными.

Таким образом, когда мы передаем УИД объекта из одной базы в другую, а в принимающей базе объект с таким УИДом не существует (поскольку он был создан по-новому, или ссылка была утеряна из-за особенностей копирования базы), или же при попытке десериализовать значение, использующее внутренние идентификаторы метаданных другой базы, мы неизбежно сталкиваемся с ошибкой «Объект не найден».

Решение 1: Передача уникальных бизнес-идентификаторов

Один из самых надежных и часто используемых подходов для идентификации объектов между разными базами, особенно когда их внутренние УИДы не совпадают, — это передача уникальных бизнес-идентификаторов. Вместо того чтобы полагаться на внутренние идентификаторы платформы 1С (как УИДы ссылок или внутренние коды сериализации), мы используем те реквизиты, которые однозначно идентифицируют объект с точки зрения бизнеса.

Какие идентификаторы мы можем использовать?

  1. Код: Для справочников часто используется реквизит Код, если он настроен как уникальный.
  2. Наименование: В некоторых случаях, если наименование гарантированно уникально, его можно использовать. Однако, будьте осторожны, так как наименования часто меняются и могут быть неуникальными.
  3. ИНН и КПП для контрагентов: Это классический пример уникальной комбинации для идентификации юридических лиц и индивидуальных предпринимателей. ИНН сам по себе уникален для физических и юридических лиц, а КПП дополняет его для организаций, имеющих филиалы или разные адреса регистрации.
  4. Другие уникальные комбинации реквизитов: В зависимости от типа объекта, это могут быть номер документа и дата, артикул товара, ФИО и дата рождения для физического лица и т.д. Главное, чтобы комбинация реквизитов была гарантированно уникальной в контексте наших информационных баз.

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

Давайте посмотрим на пример кода:


// *** Код в базе-источнике (откуда мы хотим передать данные) ***

// Получаем ссылку на контрагента, данные которого хотим передать
КонтрагентИсточник = Справочники.Контрагенты.НайтиПоКоду("000000001"); 
// Или КонтрагентИсточник = Справочники.Контрагенты.НайтиПоНаименованию("ООО Рога и Копыта");
// Или КонтрагентИсточник = МойДокумент.Контрагент; // Если контрагент берется из документа

Если КонтрагентИсточник <> Неопределено Тогда
    // Собираем уникальные бизнес-идентификаторы
    ПередаваемыеДанные = Новый Структура();
    ПередаваемыеДанные.Вставить("Код", КонтрагентИсточник.Код);
    ПередаваемыеДанные.Вставить("Наименование", КонтрагентИсточник.Наименование);
    ПередаваемыеДанные.Вставить("ИНН", КонтрагентИсточник.ИНН);
    ПередаваемыеДанные.Вставить("КПП", КонтрагентИсточник.КПП);
    // Добавляем другие необходимые реквизиты, например, контактную информацию
    ПередаваемыеДанные.Вставить("Адрес", КонтрагентИсточник.ЮридическийАдрес);
    // ... и так далее для всех нужных полей

    // Предположим, COMСоединение уже установлено и содержит ссылку на объект в базе-приемнике
    // Через который мы вызываем метод для передачи данных
    // Например, в базе-приемнике есть ОбщийМодуль.МодульОбменаСДругимиБазами,
    // в котором определена экспортная функция ПринятьКонтрагента(ДанныеКонтрагента)
    Попытка
        // Передаем структуру целиком. Структура хорошо сериализуется через COM.
        COMСоединение.ОбщиеМодули.МодульОбменаСДругимиБазами.ПринятьКонтрагента(ПередаваемыеДанные);
        Сообщить("Данные контрагента '" + КонтрагентИсточник.Наименование + "' успешно переданы.");
    Исключение
        Сообщить("Ошибка при передаче данных через COM: " + ОписаниеОшибки());
    КонецПопытки;
Иначе
    Сообщить("Контрагент с указанным кодом/наименованием не найден в базе-источнике.");
КонецЕсли;

// *** Код в базе-приемнике (в общем модуле с флагом "Вызов сервера" и "Внешнее соединение") ***
// ОбщийМодуль.МодульОбменаСДругимиБазами

Функция ПринятьКонтрагента(ДанныеКонтрагента) Экспорт
    // Попытаемся найти контрагента по ИНН и КПП
    КонтрагентПриемник = Неопределено;
    Если Не ПустаяСтрока(ДанныеКонтрагента.ИНН) Тогда
        КонтрагентПриемник = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", ДанныеКонтрагента.ИНН);
        Если КонтрагентПриемник <> Неопределено Тогда
            // Если найден по ИНН, проверим КПП (если КПП также присутствует в данных)
            Если ДанныеКонтрагента.Свойство("КПП") И Не ПустаяСтрока(ДанныеКонтрагента.КПП) Тогда
                Если КонтрагентПриемник.КПП <> ДанныеКонтрагента.КПП Тогда
                    // ИНН совпал, но КПП разный - возможно, это другой контрагент или изменилось КПП.
                    // Принимаем решение: считать, что не нашли, или обновить КПП.
                    // В данном случае считаем, что не нашли, чтобы избежать некорректного обновления.
                    КонтрагентПриемник = Неопределено;
                КонецЕсли;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;

    // Если по ИНН/КПП не нашли, попробуем найти по коду
    Если КонтрагентПриемник = Неопределено И Не ПустаяСтрока(ДанныеКонтрагента.Код) Тогда
        КонтрагентПриемник = Справочники.Контрагенты.НайтиПоКоду(ДанныеКонтрагента.Код);
    КонецЕсли;

    // Если контрагент не найден ни по каким бизнес-идентификаторам, создаем новый
    Если КонтрагентПриемник = Неопределено Тогда
        Сообщить("Контрагент с ИНН '" + ДанныеКонтрагента.ИНН + "' не найден. Создаем новый.");
        КонтрагентПриемник = Справочники.Контрагенты.СоздатьЭлемент();
        // Устанавливаем код, если он уникальный и не занят
        Попытка
            Если Не ПустаяСтрока(ДанныеКонтрагента.Код) И Справочники.Контрагенты.НайтиПоКоду(ДанныеКонтрагента.Код) = Неопределено Тогда
                КонтрагентПриемник.Код = ДанныеКонтрагента.Код;
            КонецЕсли;
        Исключение
            // Код уже занят или другая ошибка, генерируем новый
            КонтрагентПриемник.УстановитьНовыйКод(); 
            Сообщить("Не удалось установить код '" + ДанныеКонтрагента.Код + "', установлен новый: " + КонтрагентПриемник.Код);
        КонецПопытки;
    Иначе
        Сообщить("Контрагент '" + КонтрагентПриемник.Наименование + "' найден. Обновляем данные.");
    КонецЕсли;

    // Заполняем или обновляем реквизиты контрагента
    КонтрагентПриемник.Наименование = ДанныеКонтрагента.Наименование;
    КонтрагентПриемник.ИНН = ДанныеКонтрагента.ИНН;
    Если ДанныеКонтрагента.Свойство("КПП") Тогда
        КонтрагентПриемник.КПП = ДанныеКонтрагента.КПП;
    КонецЕсли;
    // ... заполняем другие реквизиты, например, контактную информацию
    Если ДанныеКонтрагента.Свойство("Адрес") Тогда
        КонтрагентПриемник.ЮридическийАдрес = ДанныеКонтрагента.Адрес;
    КонецЕсли;

    Попытка
        КонтрагентПриемник.Записать();
        Возврат КонтрагентПриемник.Ссылка;
    Исключение
        Сообщить("Ошибка при записи контрагента: " + ОписаниеОшибки());
        Возврат Неопределено;
    КонецПопытки;
КонецФункции

Этот подход делает обмен более устойчивым к различиям в структуре баз или изменениям внутренних УИДов, поскольку он ориентирован на реальные бизнес-данные.

Решение 2: Передача ссылок с использованием уникальных идентификаторов (УИД) объектов и XML-сериализации

Несмотря на то что УИДы метаданных могут различаться при копировании конфигурации, УИДы самих ссылочных объектов данных (например, конкретного элемента справочника «Контрагенты») часто остаются неизменными, если база была скопирована полным образом (например, из файла .dt). Если мы уверены, что УИДы наших объектов данных совпадают или мы готовы их принудительно устанавливать в принимающей базе, мы можем использовать их для идентификации.

Однако, как мы выяснили, просто передать ссылку или ее строковое представление через ЗначениеВСтрокуВнутр() не получится из-за специфики внутренних идентификаторов. Здесь нам на помощь приходит XML-сериализация.

Функции XMLСтрока() и XMLЗначение() предназначены именно для кросс-базового обмена и корректно работают с УИДами ссылочных объектов. Они сериализуют объект в универсальное XML-представление, которое может быть десериализовано в другой базе 1С.

Рассмотрим шаги для реализации этого подхода:

  1. В базе-источнике:

    Мы получаем ссылку на нужный объект (например, контрагента). Затем используем метод XMLСтрока(), доступный через COM-соединение, для преобразования этой ссылки в строку XML. Важно: мы используем метод XMLСтрока(), который доступен через COM-соединение к базе-источнику. Это гарантирует, что сериализация происходит в контексте источника.

  2. Передача:

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

  3. В базе-приемнике:

    Мы получаем XML-строку. Сначала мы должны восстановить из этой XML-строки объект типа УникальныйИдентификатор. Для этого используем XMLЗначение(Тип("УникальныйИдентификатор"), СтроковыйГУИД). Затем, имея объект УникальныйИдентификатор, мы используем метод ПолучитьСсылку() соответствующего справочника или документа, чтобы попытаться найти объект в текущей базе по этому УИДу. Если объект не найден, мы можем создать новый элемент и принудительно установить его УИД равным полученному, используя метод УстановитьСсылкуНового().

Давайте посмотрим на пример кода, основанный на предложениях из обсуждения на форуме, но дополненный для большей ясности:


// *** Код в базе-источнике (например, в ОбщемМодуле с флагом "Вызов сервера") ***

Функция ПолучитьXMLСтрокуКонтрагента(СсылкаНаКонтрагента) Экспорт
    Если ТипЗнч(СсылкаНаКонтрагента) = Тип("СправочникСсылка.Контрагенты") Тогда
        Возврат XMLСтрока(СсылкаНаКонтрагента);
    Иначе
        Сообщить("Передан некорректный тип ссылки для контрагента.");
        Возврат Неопределено;
    КонецЕсли;
КонецФункции;

// *** Код в клиентском приложении (или другом внешнем приложении), которое инициирует COM-соединение ***
// Предположим, у нас есть две базы: "БазаИсточник" и "БазаПриемник"
// и мы устанавливаем COM-соединения к обеим
// COM_Источник = Новый COMОбъект("V83.COMConnector");
// COM_БазаИсточник = COM_Источник.Connect("File=""C:\BaseSource"";Usr=""Админ"";Pwd=""123""");
// COM_Приемник = Новый COMОбъект("V83.COMConnector");
// COM_БазаПриемник = COM_Приемник.Connect("File=""C:\BaseReceiver"";Usr=""Админ"";Pwd=""123""");

// Получаем ссылку на контрагента в базе-источнике
КонтрагентИсточник = COM_БазаИсточник.Справочники.Контрагенты.НайтиПоКоду("000000001");

Если КонтрагентИсточник <> Неопределено Тогда
    // Получаем XML-строку ссылки из базы-источника через COM
    СтроковыйГУИД = COM_БазаИсточник.ОбщиеМодули.МойОбщийМодуль.ПолучитьXMLСтрокуКонтрагента(КонтрагентИсточник.Ссылка);
    
    Если Не ПустаяСтрока(СтроковыйГУИД) Тогда
        // Передаем эту строку в базу-приемник для обработки
        // Предположим, в базе-приемнике также есть ОбщийМодуль.МойОбщийМодуль
        // с функцией ПринятьКонтрагентаПоXMLГУИД
        Попытка
            СсылкаКонтрагентаВПриемнике = COM_БазаПриемник.ОбщиеМодули.МойОбщийМодуль.ПринятьКонтрагентаПоXMLГУИД(СтроковыйГУИД);
            Если СсылкаКонтрагентаВПриемнике <> Неопределено Тогда
                Сообщить("Контрагент успешно обработан в базе-приемнике. Ссылка: " + СсылкаКонтрагентаВПриемнике);
            Иначе
                Сообщить("Не удалось обработать контрагента в базе-приемнике.");
            КонецЕсли;
        Исключение
            Сообщить("Ошибка при вызове функции в базе-приемнике: " + ОписаниеОшибки());
        КонецПопытки;
    Иначе
        Сообщить("Не удалось получить XML-строку ссылки контрагента из базы-источника.");
    КонецЕсли;
Иначе
    Сообщить("Контрагент не найден в базе-источнике.");
КонецЕсли;


// *** Код в базе-приемнике (в ОбщемМодуле с флагом "Вызов сервера" и "Внешнее соединение") ***

Функция ПринятьКонтрагентаПоXMLГУИД(СтроковыйГУИД) Экспорт

    СсылкаПришедшая = Неопределено;
    Попытка
        // XMLЗначение() извлекает УникальныйИдентификатор из строки
        УИДОбъекта = XMLЗначение(Тип("УникальныйИдентификатор"), СтроковыйГУИД);
        
        // Попытаемся получить ссылку на объект в текущей базе по этому УИДу
        СсылкаПришедшая = Справочники.Контрагенты.ПолучитьСсылку(УИДОбъекта);
        Сообщить("Контрагент с УИД " + СтроковыйГУИД + " найден в базе-приемнике.");
        Возврат СсылкаПришедшая;

    Исключение
        Сообщить("Ошибка при получении ссылки по УИД: " + ОписаниеОшибки() + ". Возможно, объект не найден.");
        СсылкаПришедшая = Неопределено;
    КонецПопытки;

    // Если объект с таким УИДом не найден в текущей базе, создаем новый
    Если СсылкаПришедшая = Неопределено Тогда
        Сообщить("Контрагент с УИД " + СтроковыйГУИД + " не найден в базе-приемнике. Создаем новый элемент.");
        
        НовыйКонтрагент = Справочники.Контрагенты.СоздатьЭлемент();
        
        Попытка
            // Принудительно устанавливаем УИД для нового элемента.
            // Это позволит ему иметь тот же УИД, что и в базе-источнике.
            НовыйКонтрагент.УстановитьСсылкуНового(Справочники.Контрагенты.ПолучитьСсылку(УИДОбъекта));
            Сообщить("УИД для нового контрагента установлен: " + УИДОбъекта);
        Исключение
            Сообщить("Ошибка при установке ссылки нового элемента: " + ОписаниеОшибки());
            // Если установка УИД не удалась (например, из-за конфликта), 
            // можно либо сгенерировать новый УИД, либо прервать операцию.
            // Для простоты примера, продолжим с автоматическим УИДом.
            НовыйКонтрагент.УстановитьНовыйКод(); // Или просто оставим как есть, УИД сгенерируется при записи
        КонецПопытки;

        // Здесь необходимо получить остальные данные контрагента из базы-источника,
        // например, через отдельный вызов COM-метода, который возвращает структуру данных.
        // Для примера, просто заполним минимальные поля.
        // Допустим, мы также передали Наименование и ИНН через другую функцию или в той же структуре
        // (как в Решении 1).
        НовыйКонтрагент.Наименование = "Контрагент из COM (УИД: " + СтроковыйГУИД + ")";
        НовыйКонтрагент.ИНН = "1234567890"; // Пример
        // НовыйКонтрагент.КПП = "123456789"; // Пример

        Попытка
            НовыйКонтрагент.Записать();
            Сообщить("Новый контрагент создан по УИД: " + НовыйКонтрагент.Ссылка);
            Возврат НовыйКонтрагент.Ссылка;
        Исключение
            Сообщить("Ошибка при записи нового контрагента: " + ОшибкиСоОбщейФункциейПоКоду(ОписаниеОшибки()));
            Возврат Неопределено;
        КонецПопытки;
    КонецЕсли;

    Возврат Неопределено; // В случае ошибки или если не смогли найти/создать
КонецФункции

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

Дополнительные рекомендации и альтернативы

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

  1. Важность соответствия версий платформы и разрядности

    Для успешной и стабильной работы COM-соединения убедитесь, что:

    • Версии платформы 1С (например, 8.3.18.ххх) на машине, где запускается COM-клиент, и на машине (или в контексте), где работает COM-сервер, максимально совпадают. Несовпадение может привести к непредсказуемым ошибкам, вплоть до невозможности установления соединения.
    • Разрядность COM-компоненты (32-бит или 64-бит) соответствует разрядности процесса, который инициирует COM-соединение. Если клиентское приложение 32-битное, а COM-компонента зарегистрирована 64-битная (или наоборот), могут возникнуть ошибки. Убедитесь, что COM-компонента comcntr.dll зарегистрирована корректно с помощью regsvr32 для соответствующей разрядности.
    • Настройка прав доступа:
  2. Альтернативные механизмы обмена данными

    COM-соединение — это лишь один из способов взаимодействия. В зависимости от сложности задачи, объема данных и требований к надежности, мы можем рассмотреть следующие механизмы:

    • XML-сериализация / JSON-сериализация для произвольных данных:

      Если нам нужно обмениваться не только ссылками, но и сложными структурами данных (табличными частями, наборами записей регистров), мы можем сериализовать их в форматы XML или JSON. Для этого используются объекты ЗаписьXML / ЧтениеXML и ЗаписьJSON / ЧтениеJSON. Этот подход обеспечивает высокую гибкость и независимость от внутренней структуры баз, позволяя передавать данные как текстовые файлы или строки.

      
      // Пример сериализации структуры в JSON в базе-источнике
      СтруктураДанных = Новый Структура("Код, Наименование", "001", "Тестовый элемент");
      ЗаписьJSON = Новый ЗаписьJSON;
      ЗаписьJSON.УстановитьСтроку();
      ЗаписатьJSON(ЗаписьJSON, СтруктураДанных);
      СтрокаJSON = ЗаписьJSON.Закрыть();
      // СтрокаJSON передается через COM или другим способом
      
      // Пример десериализации JSON в базе-приемнике
      ЧтениеJSON = Новый ЧтениеJSON;
      ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
      ВосстановленнаяСтруктура = ПрочитатьJSON(ЧтениеJSON);
      ЧтениеJSON.Закрыть();
      
    • XDTO-сериализация:

      Этот механизм основан на фабрике XDTO, которая хранит описание всех типов данных в рамках определенного пространства имен. XDTO позволяет создавать строго типизированные схемы обмена данными и является мощным инструментом для интеграции со сложными внешними системами или для стандартизированного обмена между 1С-системами.

    • Универсальный формат обмена (EnterpriseData):

      Для комплексной и регулярной синхронизации между типовыми конфигурациями 1С, а также для обмена с внешними системами, предназначен Универсальный формат обмена EnterpriseData. Он позволяет настроить правила синхронизации документов, справочников и других объектов через выгрузку/загрузку XML-файлов или напрямую через веб-сервисы.

    • Веб-сервисы и HTTP-сервисы:

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

В целом, когда мы передаем данные между разными информационными базами 1С, всегда стремитесь к использованию универсальных и явных идентификаторов. Избегайте полагаться на внутренние идентификаторы платформы 1С, если только вы не работаете с прямыми копиями баз и не используете механизмы, специально разработанные для поддержания этих идентификаторов (как в случае с XMLСтрока() и УИДами объектов). Выбор подходящего метода обмена зависит от конкретных требований вашей задачи и архитектуры информационной системы.

← На главную