Как узнать и проверить тип реквизита в удаленной базе 1С через COM-соединение (OLE)?

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

При разработке обменов данными, интеграций и различных внешних обработок, взаимодействующих с другими базами через COM-соединение (Automation-сервер), разработчики часто сталкиваются с одной и той же неприятной особенностью. Традиционные, привычные нам механизмы определения типов данных перестают работать ожидаемым образом.

Если мы попытаемся вызвать стандартную функцию ТипЗнч() для объекта, полученного из внешней базы, или даже вызовем ее через COM-соединение как Соединение.ТипЗнч(), мы не сможем напрямую сравнить полученный тип с локальными типами нашей текущей базы. Причина кроется в специфике маршалинга (передачи данных) между процессами: объект типа Тип при пересечении границы COM-соединения автоматически упаковывается в универсальную обертку COMОбъект. Наша локальная база видит в нем лишь сторонний COM-объект, из-за чего прямое сравнение вида ТипИзCOM = Тип("СправочникСсылка.Контрагенты") всегда будет возвращать Ложь.

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

Почему возникает эта проблема? Выясняем причину

Давайте детально разберем физику процесса взаимодействия. Когда мы работаем в нашей «родной» (локальной) информационной базе и подключаемся к «внешней» базе через OLE/COM, создается отдельный программный контекст. Все данные, передаваемые из внешней базы, транслируются в простые типы (числа, строки, булево, дата) или оборачиваются в специальный тип COMОбъект.

Если у нас есть реквизит во внешней базе, который может содержать значения разных типов (например, составной тип данных, способный хранить как ссылку на справочник, так и значение перечисления), нам критически важно определить его тип «на лету». Однако:

  1. Попытка выполнить ТипЗнч(Источник.Реквизит) на нашей стороне вернет тип COMОбъект.
  2. Попытка выполнить Соединение.ТипЗнч(Источник.Реквизит) вернет объект, который на нашей стороне опять же определится как COMОбъект, поскольку сам тип 1С во внешней базе не является примитивным типом и не может быть неявно преобразован в локальный тип Тип.

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

Решение 1. Использование XML-сериализации через XMLТипЗнч() (Самый быстрый и надежный способ)

Проанализируем наиболее изящный способ обхода ограничений COM. Он заключается в использовании механизмов XML-сериализации платформы 1С. Функция глобального контекста XMLТипЗнч() (или её англоязычный синоним XMLTypeOf()), будучи вызванной в контексте удаленной базы, возвращает специальный объект ТипДанныхXML.

Самое главное свойство этого объекта — TypeName (или ИмяТипа). Оно содержит строковое представление типа в формате XML. Поскольку строки передаются через COM-соединение без каких-либо искажений и не оборачиваются в COM-объекты, мы получаем точное имя типа в виде обычного текста!

Посмотрим на практический пример реализации этого метода:


// Предположим, что Соединение — это наше активное COM-соединение с внешней базой.
// Источник — это объект (например, документ или элемент справочника) из внешней базы.
// НаимРеквизита — имя проверяемого реквизита (строка).

ЗначениеРеквизита = Источник[НаимРеквизита];

// Вызываем метод XMLТипЗнч через COM-соединение
ТипXML = Соединение.XMLТипЗнч(ЗначениеРеквизита);

// Получаем имя типа в виде обычной строки
ИмяТипаXML = ТипXML.TypeName; 

// Теперь мы можем легко анализировать полученную строку на нашей стороне
Если Найти(ИмяТипаXML, "CatalogRef") > 0 Тогда
    // Это ссылка на справочник во внешней базе
    Сообщить("Это справочник: " + ИмяТипаXML);
ИначеЕсли Найти(ИмяТипаXML, "EnumRef") > 0 Тогда
    // Это ссылка на перечисление во внешней базе
    Сообщить("Это перечисление: " + ИмяТипаXML);
ИначеЕсли Найти(ИмяТипаXML, "DocumentRef") > 0 Тогда
    // Это ссылка на документ во внешней базе
    Сообщить("Это документ: " + ИмяТипаXML);
Иначе
    // Обработка простых типов (string, decimal, boolean, dateTime)
    Сообщить("Простой тип данных: " + ИмяТипаXML);
КонецЕсли;

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

Решение 2. Использование метода ПолноеИмя() метаданных объекта

Если мы заранее знаем или предполагаем, что проверяемый реквизит во внешней базе является ссылочным типом (справочником, документом или перечислением), мы можем обратиться к его метаданным непосредственно через COM-соединение. Метод ПолноеИмя() объекта метаданных возвращает строковое описание (например, "Справочник.Номенклатура"), которое отлично передается на нашу сторону.

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


ЗначениеРеквизита = Источник[НаимРеквизита];

// Сначала проверяем, заполнено ли значение реквизита во внешней базе
Если Соединение.ЗначениеЗаполнено(ЗначениеРеквизита) Тогда
    
    // Используем конструкцию Попытка-Исключение на случай, если значение окажется примитивного типа
    Попытка
        // Получаем полное имя объекта метаданных из COM
        ПолноеИмяНаСторонеCOM = ЗначениеРеквизита.Метаданные().ПолноеИмя();
        
        // Теперь анализируем строку метаданных
        Если Лев(ПолноеИмяНаСторонеCOM, 10) = "Справочник" Тогда
            // Выполняем логику для работы со справочниками
            Сообщить("Перед нами справочник: " + ПолноеИмяНаСторонеCOM);
            
        ИначеЕсли Лев(ПолноеИмяНаСторонеCOM, 12) = "Перечисление" Тогда
            // Выполняем логику для работы с перечислениями
            Сообщить("Перед нами перечисление: " + ПолноеИмяНаСторонеCOM);
            
        ИначеЕсли Лев(ПолноеИмяНаСторонеCOM, 8) = "Документ" Тогда
            // Выполняем логику для работы с документами
            Сообщить("Перед нами документ: " + ПолноеИмяНаСторонеCOM);
        КонецЕсли;
        
    Исключение
        // Сюда мы попадем, если у значения нет метаданных (например, число, строка, булево, дата)
        ТипПримитивный = Соединение.XMLТипЗнч(ЗначениеРеквизита).TypeName;
        Сообщить("Это простой тип данных: " + ТипПримитивный);
    КонецПопытки;
    
Иначе
    Сообщить("Значение реквизита не заполнено, определить тип по метаданным значения невозможно.");
КонецЕсли;

Решение 3. Сравнение типов полностью внутри контекста COM-соединения

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

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

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


// Получаем тип реквизита. На нашей стороне это COMОбъект, но внутри COM-базы это полноценный тип
ТипРеквизита = Соединение.ТипЗнч(Источник[НаимРеквизита]);

// Проверяем, входит ли полученный тип в состав типов всех справочников удаленной базы
ЭтоСправочник = Соединение.Справочники.ТипВсеСсылки().СодержитТип(ТипРеквизита);

// Проверяем, входит ли тип в состав всех перечислений удаленной базы
ЭтоПеречисление = Соединение.Перечисления.ТипВсеСсылки().СодержитТип(ТипРеквизита);

// Проверяем, входит ли тип в состав всех документов удаленной базы
ЭтоДокумент = Соединение.Документы.ТипВсеСсылки().СодержитТип(ТипРеквизита);

// Применяем логическое ветвление на основании полученных булевых флагов
Если ЭтоСправочник Тогда
    Сообщить("Реквизит является справочником удаленной базы");
ИначеЕсли ЭтоПеречисление Тогда
    Сообщить("Реквизит является перечислением удаленной базы");
ИначеЕсли ЭтоДокумент Тогда
    Сообщить("Реквизит является документом удаленной базы");
КонецЕсли;

Решение 4. Особый случай: сопоставление значений Перечислений через COM

Отдельной и очень частой задачей при обмене является сопоставление значений перечислений. Мало просто узнать, что перед нами перечисление определенного типа. Нам нужно получить конкретное значение этого перечисления и найти его аналог в нашей локальной базе. Напрямую сравнить COM-объект значения перечисления с локальным значением перечисления мы не можем.

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


ЗначениеРеквизита = Источник[НаимРеквизита];

// Убедимся, что это перечисление (используя логику из Решения 2)
ПолноеИмяНаСторонеCOM = ЗначениеРеквизита.Метаданные().ПолноеИмя();

Если Лев(ПолноеИмяНаСторонеCOM, 12) = "Перечисление" Тогда
    
    // Получаем чистое имя перечисления (например, "ЮрФизЛицо" из "Перечисление.ЮрФизЛицо")
    ИмяПеречисления = ЗначениеРеквизита.Метаданные().Имя;
    
    // Находим числовой индекс значения перечисления во внешней базе (индексация начинается с 0)
    ИндексЗначения = Соединение.Перечисления[ИмяПеречисления].Индекс(ЗначениеРеквизита);
    
    // По индексу получаем строковое имя значения перечисления (например, "ФизическоеЛицо")
    ИмяЗначенияПеречисления = Соединение.Метаданные.Перечисления[ИмяПеречисления].ЗначенияПеречисления.Получить(ИндексЗначения).Имя;
    
    // Теперь мы можем без труда получить точно такое же значение в нашей локальной базе!
    Попытка
        ЛокальноеЗначениеПеречисления = Перечисления[ИмяПеречисления][ИмяЗначенияПеречисления];
        Сообщить("Успешно сопоставили перечисление: " + ЛокальноеЗначениеПеречисления);
    Исключение
        Сообщить("В локальной базе отсутствует значение " + ИмяЗначенияПеречисления + " для перечисления " + ИмяПеречисления);
    КонецПопытки;
    
КонецЕсли;

Резюме и рекомендации

Мы подробно проанализировали проблему определения типов данных при работе с COM-соединением (OLE) в 1С и разобрали четыре эффективных метода её решения. В зависимости от специфики вашей задачи, рекомендуем придерживаться следующих правил:

← На главную