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

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

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

В этой статье мы подробно разберем, как программно установить соединение с другой базой 1С версии 8.3 (удобнее через автоматизация регулярного обмена и синхронизации данных через COM), как правильно организовать выборку данных и как корректно перенести элементы справочника, избегая типичных ошибок, связанных с несоответствием типов и уникальных идентификаторов. Проанализируем весь процесс от настройки среды до написания кода переноса.

Подготовка к работе и выбор типа соединения

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

  1. V83.Application — запускает полноценный экземпляр приложения (может быть видимым или скрытым). Это работает медленнее и требует больше ресурсов, но позволяет использовать функции клиентского приложения.
  2. V83.COMConnector — запускает серверный процесс (внешнее соединение). Это наиболее оптимальный вариант для обмена данными, так как он работает быстрее и не требует прорисовки интерфейса.

Для наших задач по переносу справочников (поможет универсальный обмен справочниками и документами через COM) мы будем использовать именно V83.COMConnector. Важно убедиться, что библиотека comcntr.dll корректно зарегистрирована в операционной системе. Если вы сталкиваетесь с ошибкой "Класс не зарегистрирован", часто помогает перерегистрация библиотеки через консоль командной строки (также существуют возможности использовать Python для создания COM-объектов).

Установка соединения с внешней базой

Рассмотрим процесс подключения. Нам понадобится сформировать строку соединения. Для файлового и клиент-серверного вариантов она различается. Разберем универсальный подход, где мы попытаемся установить соединение внутри блока Попытка ... Исключение, чтобы корректно обработать возможные ошибки доступа (например, неверный логин или пароль).

Создадим функцию подключения, которая вернет нам COM-объект базы-источника. Обратите внимание на использование метода Connect.


Функция ПодключитьсяКБазеИсточнику(ПутьКБазе, Пользователь, Пароль, Файловая = Истина) Экспорт
    
    // Инициализируем COM-коннектор
    Попытка
        V83COMConnector = Новый COMОбъект("V83.COMConnector");
    Исключение
        Сообщить("Не удалось создать объект V83.COMConnector. Проверьте установку платформы.");
        Возврат Неопределено;
    КонецПопытки;
    
    // Формируем строку соединения
    Если Файловая Тогда
        СтрокаСоединения = "File=""" + ПутьКБазе + """;";
    Иначе
        // Пример для серверной базы: Srvr=ServerName;Ref=BaseName;
        СтрокаСоединения = "Srvr=""" + ПутьКБазе + """;Ref=""BaseName"";"; // Здесь нужно уточнить параметры
    КонецЕсли;
    
    // Добавляем параметры аутентификации
    СтрокаСоединения = СтрокаСоединения + "Usr=""" + Пользователь + """;Pwd=""" + Пароль + """;";
    
    // Попытка соединения
    Попытка
        БазаИсточник = V83COMConnector.Connect(СтрокаСоединения);
        Сообщить("Соединение с базой успешно установлено!");
        Возврат БазаИсточник;
    Исключение
        ИнформацияОбОшибке = ИнформацияОбОшибке();
        Сообщить("Ошибка подключения: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
        Возврат Неопределено;
    КонецПопытки;

КонецФункции

Когда соединение установлено, переменная БазаИсточник содержит контекст внешней базы. Через нее мы можем обращаться к менеджерам справочников, документов, перечислений и, что самое важное, выполнять запросы.

Получение данных из источника

Многие начинающие разработчики совершают ошибку, перебирая справочники через Справочники.Номенклатура.Выбрать() через COM-соединение — для этого есть выгрузка и загрузка данных между базами 1С через COM. Это работает крайне медленно на больших объемах данных, так как каждый вызов метода через COM вызывает накладные расходы (маршалинг вызовов). Гораздо эффективнее выполнить запрос на стороне базы-источника и получить готовую выборку.

Посмотрим, как правильно написать запрос к внешней базе. Обратите внимание: метод NewObject используется для создания объектов (например, Запроса) именно в контексте внешней базы.


Функция ПолучитьДанныеНоменклатуры(БазаИсточник)
    
    // Создаем запрос в контексте внешней базы
    ЗапросCOM = БазаИсточник.NewObject("Запрос");
    
    // Текст запроса пишем как обычно, но учитываем структуру метаданных источника
    ЗапросCOM.Текст = 
        "ВЫБРАТЬ
        |   Номенклатура.Ссылка КАК Ссылка,
        |   Номенклатура.Код КАК Код,
        |   Номенклатура.Наименование КАК Наименование,
        |   Номенклатура.Артикул КАК Артикул,
        |   Номенклатура.ЭтоГруппа КАК ЭтоГруппа,
        |   Номенклатура.Родитель.Код КАК КодРодителя
        |ИЗ
        |   Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |   НЕ Номенклатура.ПометкаУдаления";
        
    // Выполняем запрос
    РезультатCOM = ЗапросCOM.Выполнить();
    ВыборкаCOM = РезультатCOM.Выбрать();
    
    Возврат ВыборкаCOM;
    
КонецФункции

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

Создание и синхронизация объектов

Теперь перейдем к самому ответственному этапу — созданию элементов в нашей текущей базе. Мы будем перебирать выборку, полученную из внешней базы. Для каждого элемента нам нужно:

  1. Проверить, существует ли он уже в нашей базе (синхронизация).
  2. Если не существует — создать новый.
  3. Если существует — обновить реквизиты (опционально).
  4. Заполнить данные.

Для синхронизации чаще всего используют уникальный идентификатор (GUID) или Код/Артикул. Профессиональный подход подразумевает синхронизацию по Уникальному Идентификатору, так как это гарантирует однозначное соответствие объектов, даже если их переименовали. Однако, метод GetRef через COM возвращает COM-объект, из которого нужно извлечь строковое представление UUID.

Разберем алгоритм создания элементов. Используем метод String(COMСсылка.UUID()) внутри COM-контекста для получения строки GUID.


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

КонецПроцедуры

Проблема передачи ссылочных типов

Остановимся подробнее на самой частой проблеме. Вы не можете просто написать НашОбъект.Родитель = COMВыборка.Родитель. Это вызовет ошибку несовместимости типов, так как справа — COM-объект, а слева ожидается ссылка 1С текущей базы.

Чтобы заполнить ссылочное поле (например, Родитель, СтавкаНДС, ЕдиницаИзмерения), нам необходимо найти аналог этого объекта в текущей базе. Обычно это делается в два этапа:

  1. В запросе к внешней базе мы выбираем поля поиска (Код, Наименование, ИНН для контрагентов).
  2. В текущей базе используем методы НайтиПоКоду или НайтиПоНаименованию для поиска соответствующей ссылки.

Рассмотрим пример функции для поиска или создания группы (Родителя) для номенклатуры. Это важно для сохранения иерархии.


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

Затем в основном цикле мы применяем эту функцию:

ОбъектНоменклатура.Родитель = ПолучитьРодителя(ВыборкаCOM.КодРодителя);

Передача специальных типов: Перечисления

Отдельного внимания заслуживают Перечисления. Значение перечисления из COM-базы также является COM-объектом. Мы не можем присвоить его напрямую. Самый надежный способ переноса — через строковое имя значения перечисления.

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

Пример получения имени перечисления из COM-объекта (внутри цикла обхода):


// Получаем строковое имя перечисления из внешней базы
// Используем метод String() внешней базы или свойство метаданных Name
ИмяЗначенияПеречисления = БазаИсточник.String(ВыборкаCOM.ВидНоменклатуры); // Это может вернуть синоним! Осторожно.

// Лучше использовать XMLString для надежности, если доступно, или имя через метаданные:
// Индекс = БазаИсточник.Enums.ВидыНоменклатуры.IndexOf(ВыборкаCOM.ВидНоменклатуры);
// ИмяЗначения = БазаИсточник.Metadata.Enums.ВидыНоменклатуры.EnumValues.Get(Индекс).Name;

// Простой вариант, если синонимы не менялись:
Попытка
    ОбъектНоменклатура.ВидНоменклатуры = Перечисления.ВидыНоменклатуры[ИмяЗначенияПеречисления];
Исключение
    // Обработка, если значение не найдено
КонецПопытки;

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

При работе с большими объемами данных через COM-соединение следует помнить о производительности:

  1. Минимизируйте количество вызовов через "точку". Каждый раз, когда вы обращаетесь к свойству COM-объекта (например, Выборка.Родитель.Код), происходит удаленный вызов. Старайтесь выбирать все нужные данные сразу в запросе (как мы сделали с Родитель.Код).
  2. Используйте транзакции. Запись тысяч элементов по одному будет медленной. Оберните цикл записи (например, пакетами по 100 или 1000 элементов) в НачатьТранзакцию() и ЗафиксироватьТранзакцию(). Это значительно ускорит процесс.
  3. Освобождение памяти. После завершения работы принудительно очищайте переменную соединения: БазаИсточник = Неопределено. Хотя сборщик мусора должен сработать сам, явное освобождение ресурсов при работе с COM является хорошей практикой.

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

← На главную