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

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

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

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

Причина проблемы: почему падает стандартный код

Проанализируем классическую ситуацию. Разработчик обходит выборку изменений в цикле:


УзелВншСист = БД.ПланыОбмена.Обмен.НайтиПоКоду("02");
ВыборкаИзменений = БД.ПланыОбмена.ВыбратьИзменения(УзелВншСист, 1);

Пока ВыборкаИзменений.Следующий() Цикл
    Данные = ВыборкаИзменений.Получить();
    // Здесь нужно понять, с каким объектом мы работаем
КонецЦикла;

Если в выборку попадает справочник, то мы можем легко получить его имя через обращение: Данные.Ссылка.Метаданные().Name. Но если измененным объектом оказывается регистр сведений или регистр накопления, система вернет тип РегистрСведенийНаборЗаписей. У этого объекта отсутствует свойство Ссылка, что приводит к аварийному прерыванию выполнения кода.

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

Решение 1. Использование конструкции Попытка — Исключение

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

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


Пока ВыборкаИзменений.Следующий() Цикл
    Данные = ВыборкаИзменений.Получить();
    
    Попытка
        // Пробуем получить имя через Ссылку (для ссылочных типов)
        ДанныеИмя = Данные.Ссылка.Метаданные().Name;    
    Исключение
        // Если Ссылки нет, значит это набор записей регистра
        ДанныеИмя = Данные.Метаданные().Name;    
    КонецПопытки;
    
    // Дальнейшая обработка
КонецЦикла;

Преимущества: Простота реализации, не требует глубоких знаний механизмов сериализации.

Недостатки: Использование конструкции Попытка...Исключение для управления логикой приложения считается плохой практикой (антипаттерном). Кроме того, обработка исключений в 1С — достаточно ресурсоемкая операция. Если в выборке будут тысячи измененных регистров, скорость работы алгоритма существенно снизится.

Решение 2. Прямое обращение к методу Метаданные()

Мало кто помнит, но платформа «1С:Предприятие» предоставляет единый метод Метаданные() для подавляющего большинства прикладных типов данных. Этот метод успешно работает как для ссылок (СправочникСсылка, ДокументСсылка), так и для самих объектов (СправочникОбъект) и для наборов записей регистров (РегистрСведенийНаборЗаписей).

Поэтому мы можем полностью отказаться от проверок на «ссылочность» и сократить весь код до одной безопасной строки:


ДанныеИмя = Данные.Метаданные().Name; // Для русскоязычной локализации можно использовать .Имя

Этот вызов вернет чистое имя объекта метаданных (например, "Номенклатура" или "ЦеныНоменклатуры") для любого из обрабатываемых типов без вызова исключений.

Решение 3. Использование XMLТипЗнч (Надежный системный подход)

Если вам требуется не просто получить имя, а точно классифицировать тип объекта (понять, ссылка это или набор записей), отличным решением станет механизм XML-сериализации. В контексте COM-соединения мы можем вызвать метод XMLТипЗнч(), который возвращает специальный объект типа ТипДанныхXML.

Посмотрим на пример работы с этим механизмом:


ТипXML = БД.XMLТипЗнч(Данные);
ИмяТипаXML = ТипXML.TypeName; 
// Вернет, например: "CatalogRef.Номенклатура" или "InformationRegisterRecordSet.Цены"

Полученную строку очень легко проанализировать с помощью функций работы со строками:


ЧастиИмени = СтрРазделить(ИмяТипаXML, ".");
ВидОбъекта = ЧастиИмени[0]; // "CatalogRef", "DocumentRef" или "InformationRegisterRecordSet"
ИмяОбъекта = ЧастиИмени[1]; // "Номенклатура", "Цены"

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

Решение 4. Корректное сравнение типов через СравнениеЗначений

В процессе обсуждения часто возникает вопрос: «Почему нельзя использовать привычную функцию ТипЗнч()?».

Дело в том, что если вы вызовете её локально в своей базе, передав туда полученный по COM объект:

ТипЛокальный = ТипЗнч(Данные); // Вернет тип "COMОбъект"

Система определит его просто как интерфейс операционной системы, что не даст нам никакой полезной информации.

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


ТипВБД = БД.ТипЗнч(Данные); // Теперь внутри переменной хранится тип из внешней базы данных

Однако, напрямую сравнить этот тип с локальным типом (например, Тип("СправочникСсылка.Номенклатура")) мы не сможем, так как они принадлежат разным пространствам памяти. Чтобы решить эту задачу, нам потребуется создать объект сравнения значений непосредственно во внешней базе:


Сравнение = БД.NewObject("СравнениеЗначений");

// Получаем тип, с которым хотим сравнить (например, тип конкретного справочника во внешней базе)
ТипДляСравнения = БД.ТипЗнч(БД.Справочники.Номенклатура.ПустаяСсылка());

РезультатСравнения = Сравнение.Сравнить(БД.ТипЗнч(Данные), ТипДляСравнения);

Если РезультатСравнения = 0 Тогда
    // Типы полностью совпадают
Иначе    
    // Типы различны    
КонецЕсли;

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


ОбъектМД = БД.Метаданные.НайтиПоТипу(БД.ТипЗнч(Данные));
Если ОбъектМД <> Неопределено Тогда
    ДанныеИмя = ОбъектМД.Имя;
КонецЕсли;

Важный нюанс: Борьба за производительность

Разберем критически важный аспект работы с COM. Каждое обращение «через точку» к свойствам COM-объекта (например, Данные.Метаданные().Name) инициирует отдельный межпроцессный вызов (round-trip). Если в цикле обмена обрабатываются десятки тысяч измененных объектов, программа начнет сильно «тормозить», так как сеть или процессор будут перегружены постоянными мелкими запросами между базами.

Для оптимизации процесса настоятельно рекомендуется использовать один из двух подходов:

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