Работа с технологией COMConnector — одна из классических задач при интеграции различных информационных баз на платформе 1С:Предприятие. Однако начинающие разработчики часто сталкиваются с неочевидными на первый взгляд ошибками, самой распространенной из которых является "Несоответствие типов" при попытке записи или проведения документов.
В этой статье мы подробно разберем, почему возникает эта ошибка, как правильно сопоставлять данные между базами-источниками и базами-приемниками, а также рассмотрим лучшие практики по синхронизации объектов, оптимизации производительности и администрированию COM-соединений.
Проанализируем типичную ситуацию. Разработчик подключается к внешней базе через COM, выполняет там запрос, получает таблицу результатов и начинает обходить ее в цикле. При попытке заполнить реквизиты нового документа в локальной базе кодом вида Документ.Организация = стр.Организация или НовыйОбъект.Наименование = стр.Контрагент платформа выбрасывает исключение.
Причина кроется в физической природе COM-объектов. Переменная стр.Контрагент или стр.Организация во внешней выборке — это не привычная локальная ссылка типа СправочникСсылка.Контрагенты, а так называемый COM-объект (прокси-объект), указывающий на область памяти в другом процессе (или на другом сервере). Локальная база 1С ничего не знает о внутренних идентификаторах объектов удаленной базы и не может напрямую записать прокси-ссылку в реквизит своей таблицы данных.
Для успешного обмена нам необходимо «приземлить» полученные данные: извлечь из COM-объекта примитивные типы (строки, числа, даты) и по ним выполнить поиск или создание аналогичных объектов в локальной базе-приемнике.
---
Самый простой и очевидный способ сопоставления объектов — это поиск по уникальным текстовым реквизитам. Для контрагентов идеальной парой являются ИНН и КПП, а для организаций — ИНН. Если поиск не дал результатов, мы создаем новый элемент локально.
Рассмотрим по шагам, как переписать логику получения данных. Во-первых, в запросе во внешней базе лучше сразу выбирать текстовые поля, а не ссылочные объекты целиком. Во-вторых, поиск в базе-приемнике следует вынести в отдельную надежную функцию.
Посмотрим на пример реализации такого поиска и создания контрагента:
Функция НайтиИлиСоздатьКонтрагентаЛокально(Знач Наименование, Знач ИНН, Знач КПП) Экспорт
// 1. Попытка поиска по ИНН и КПП (для юрлиц)
Если Не ПустаяСтрока(ИНН) Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН
| И (&БезКПП ИЛИ Контрагенты.КПП = &КПП)";
Запрос.УстановитьПараметр("ИНН", ИНН);
Запрос.УстановитьПараметр("КПП", КПП);
Запрос.УстановитьПараметр("БезКПП", ПустаяСтрока(КПП));
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Ссылка;
КонецЕсли;
КонецЕсли;
// 2. Попытка поиска по наименованию, если по ИНН не нашли
СсылкаПоНаименованию = Справочники.Contragents.НайтиПоНаименованию(Наименование, Истина);
Если ЗначениеЗаполнено(СсылкаПоНаименованию) Тогда
Возврат СсылкаПоНаименованию;
КонецЕсли;
// 3. Создание нового контрагента, если объект не найден
НовыйКонтрагент = Справочники.Контрагенты.СоздатьЭлемент();
НовыйКонтрагент.Наименование = Наименование;
НовыйКонтрагент.НаименованиеПолное = Наименование;
НовыйКонтрагент.ИНН = ИНН;
НовыйКонтрагент.КПП = КПП;
// Заполняем другие обязательные реквизиты (например, ВидКонтрагента)
НовыйКонтрагент.ЮридическоеФизическоеЛицо = Перечисления.ЮридическоеФизическоеЛицо.ЮрЛицо;
Попытка
НовыйКонтрагент.Записать();
Возврат НовыйКонтрагент.Ссылка;
Исключение
ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка, , ,
"Не удалось записать контрагента: " + ОписаниеОшибки());
Возврат Справочники.Контрагенты.ПустаяСсылка();
КонецПопытки;
КонецФункции
---
Синхронизация по ИНН или наименованию имеет очевидный минус: пользователи могут изменить наименование или ошибиться в ИНН, что приведет к появлению дублей. Самым надежным способом синхронизации данных между базами 1С является использование Уникального Идентификатора (UUID).
Передать ссылку напрямую через COM нельзя, но мы можем получить строковое представление UUID из базы-источника, передать его как строку и применить в базе-приемнике. Для этого используется метод контекста COM-соединения String().
Разберем подробнее алгоритм интеграции с использованием UUID:
// В цикле обработки результатов COM-запроса:
Для Каждого СтрИзИсточника Из РезультатЗапроса COM Цикл
// Получаем UUID контрагента из COM-базы в виде строки
СтрокаUUID = Подключение.String(СтрИзИсточника.Контрагент.УникальныйИдентификатор());
НаименованиеКонтрагента = Подключение.String(СтрИзИсточника.Контрагент.Наименование);
// Преобразуем строку в локальный УникальныйИдентификатор
ЛокальныйUUID = Новый УникальныйИдентификатор(СтрокаUUID);
// Ищем локальную ссылку по полученному UUID
ЛокальнаяСсылкаКонтрагента = Справочники.Контрагенты.ПолучитьСсылку(ЛокальныйUUID);
// Проверяем, существует ли физически элемент в базе с такой ссылкой
Если ЛокальнаяСсылкаКонтрагента.ПолучитьОбъект() = Неопределено Тогда
// Создаем новый элемент справочника
НовыйКонтрагент = Справочники.Контрагенты.СоздатьЭлемент();
// КРИТИЧЕСКИ ВАЖНО: Устанавливаем ту же ссылку, что и в базе-источнике!
НовыйContragent.УстановитьСсылкуНового(ЛокальнаяСсылкаКонтрагента);
НовыйКонтрагент.Наименование = НаименованиеКонтрагента;
// Заполняем остальные реквизиты...
Попытка
НовыйКонтрагент.Записать();
Исключение
Сообщить("Ошибка записи контрагента: " + ОписаниеОшибки());
Продолжить;
КонецПопытки;
КонецЕсли;
// Теперь у нас есть гарантированно сопоставленная локальная ссылка
// Используем ее при создании локального документа
НовыйДокумент = Документы.ОтчетКомиссионераОПродажах.СоздатьДокумент();
НовыйДокумент.Дата = СтрИзИсточника.Дата;
НовыйДокумент.Контрагент = ЛокальнаяСсылкаКонтрагента;
// Заполнение остальных реквизитов...
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
Использование метода УстановитьСсылкуНового() позволяет гарантировать, что элементы справочника в обеих базах будут иметь абсолютно идентичные UUID. Это навсегда решает проблему повторного поиска и исключает появление дублей.
---
Рассмотрим важную архитектурную проблему, из-за которой внешние обработки обмена могут работать часами. В коде автора темы обход результатов запроса выполняется напрямую: Для Каждого стр Из Результат Цикл. Если в результате запроса 5000 строк, то при каждом обращении к свойству (например, стр.Дата, стр.Организация) платформа осуществляет вызов через границу процессов операционной системы (cross-process call).
Тысячи таких сетевых обращений катастрофически снижают производительность. Как делать правильно?
---
При промышленной эксплуатации интеграционных решений на базе COM-соединений часто возникают инфраструктурные сложности. Проанализируем основные из них:
Исключение, а переменная COM-соединения не очищается, лицензия останется занятой. Всегда пишите явное обнуление переменных:
Попытка
// Код обмена...
Исключение
Сообщить("Ошибка: " + ОписаниеОшибки());
КонецПопытки;
// Обязательная очистка ссылок
Подключение = Неопределено;
СоединениеCOM = Неопределено;
comcntr.dll зарегистрирована как 32-битная (или наоборот), вы получите ошибку "Класс не зарегистрирован". Для решения этой проблемы необходимо зарегистрировать библиотеку в службе компонентов Windows (COM+) в качестве выделенного приложения (Out-of-Process), которое будет запускаться через системный утилитарный процесс dllhost.exe.V83.COMConnector напрямую не получится.С учетом сложности настройки COM-компонент, проблем с блокировкой лицензий и платформенной зависимости, в современной разработке 1С использование COM-соединений считается устаревшим подходом (legacy). При проектировании новых систем интеграции рекомендуется рассмотреть использование кроссплатформенных альтернатив: