Как выгрузить результаты запроса по COM-соединению в дерево значений на управляемой форме 1С?

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

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

Причина ошибки при работе с COM-объектами

Разберем, почему возникает исключение при выполнении следующего кода:


Дерево = Запрос.Выполнить().Выгрузить(УдаленнаяБаза.ОбходРезультатаЗапроса.ПоГруппировкамСИерархией); 
ЗначениеВРеквизитФормы(Дерево, "Автомобили");

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

Вторая распространенная проблема — попытка обойти полученное дерево или выборку удаленной базы в цикле с добавлением строк в локальное дерево. Каждое обращение к свойству или методу COM-объекта (например, Выборка.Наименование, Строка.Строки.Добавить()) генерирует отдельный межпроцессный вызов (RPC). При обработке сотен или тысяч элементов этот процесс занимает десятки секунд.

Способ 1. Быстрая передача через сериализацию во внутреннюю строку

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

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

  1. Выполняем запрос с иерархией в удаленной базе.
  2. Выгружаем результат запроса в дерево значений на стороне COM-сервера.
  3. Преобразуем дерево в строку с помощью ЗначениеВСтрокуВнутр на стороне удаленной базы.
  4. В текущей базе восстанавливаем локальный объект методом ЗначениеИзСтрокиВнутр.
  5. Помещаем восстановленное дерево в реквизит формы.

Посмотрим на пример программного кода:


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

Способ 2. Выгрузка в плоскую таблицу и локальное построение дерева

Если требуется полный контроль над созданием узлов или строковая сериализация неприменима, можно передать плоскую таблицу данных и быстро построить иерархию в текущей базе с помощью объекта Соответствие за время O(N). Это исключает медленный поиск по дереву методом Найти() и избавляет от тысяч COM-обращений.

Разберем по шагам создание дерева из плоского списка:

  1. В удаленной базе выполняем плоский запрос, выбирая поля элемента и идентификатор его родителя.
  2. Выгружаем результат в ТаблицаЗначений удаленной базы и сериализуем либо передаем через XML.
  3. Локально создаем ДеревоЗначений и объект Соответствие, в котором сохраняем созданные строки дерева по ключу кода или идентификатора.
  4. За один проход распределяем строки по соответствующим родительским узлам.

Посмотрим на пример кода:


&НаСервере
Процедура ЗагрузитьЧерезПлоскуюТаблицу()
    УдаленнаяБаза = ВнешниеСоединения.ПолучитьПодключениеКСервернойБД();
    
    Запрос = УдаленнаяБаза.NewObject("Запрос");
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Автомобили.Код КАК Код,
        |    Автомобили.Наименование КАК Наименование,
        |    Автомобили.ЭтоГруппа КАК ЭтоГруппа,
        |    ЕСТЬNULL(Автомобили.Родитель.Код, """") КАК КодРодителя
        |ИЗ
        |    Справочник.Автомобили КАК Автомобили
        |УПОРЯДОЧИТЬ ПО
        |    Автомобили.ЭтоГруппа УБЫВ,
        |    Автомобили.Наименование";
    
    ТаблицаCOM = Запрос.Выполнить().Выгрузить();
    СтрокаТаблицы = УдаленнаяБаза.ЗначениеВСтрокуВнутр(ТаблицаCOM);
    ЛокальнаяТаблица = ЗначениеИзСтрокиВнутр(СтрокаТаблицы);
    
    // Создаем результирующее локальное дерево значений
    ДеревоРезультат = Новый ДеревоЗначений;
    ДеревоРезультат.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка"));
    ДеревоРезультат.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
    
    КэшУзлов = Новый Соответствие;
    
    Для Каждого СтрДанных Из ЛокальнаяТаблица Цикл
        РодительскийУзел = КэшУзлов[СтрДанных.КодРодителя];
        
        Если РодительскийУзел = Неопределено Тогда
            НоваяСтрока = ДеревоРезультат.Строки.Добавить();
        Иначе
            НоваяСтрока = РодительскийУзел.Строки.Добавить();
        КонецЕсли;
        
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрДанных);
        
        Если СтрДанных.ЭтоГруппа Тогда
            КэшУзлов.Вставить(СтрДанных.Код, НоваяСтрока);
        КонецЕсли;
    КонецЦикла;
    
    ЗначениеВРеквизитФормы(ДеревоРезультат, "Автомобили");
КонецПроцедуры

Способ 3. Использование XML / СериализатораXDTO

В случаях, когда прямой вызов ЗначениеВСтрокуВнутр ограничен архитектурными стандартами, аналогичную процедуру можно выполнить через стандартную сериализацию XDTO в строку XML:


// В удаленной базе
ЗаписьXML = УдаленнаяБаза.NewObject("ЗаписьXML");
ЗаписьXML.УстановитьСтроку();
УдаленнаяБаза.СериализаторXDTO.ЗаписатьXML(ЗаписьXML, ДеревоCOM);
СтрокаXML = ЗаписьXML.Закрыть();

// В локальной базе
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXML);
ЛокальноеДерево = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
ЧтениеXML.Закрыть();

ЗначениеВРеквизитФормы(ЛокальноеДерево, "Автомобили");

Важные рекомендации при интеграции через COM

  1. Передавайте только примитивные типы: избегайте выборки ссылок на документы, справочники или перечисления. Используйте строковые представления или уникальные идентификаторы (GUID), приведенные к строке: Строка(Ссылка.УникальныйИдентификатор()).
  2. Минимизируйте количество вызовов: сериализуйте всю коллекцию за одно действие на стороне COM-сервера, чтобы не тратить время на тысячи межпроцессных обращений к каждой отдельной строке.
  3. Учитывайте кроссплатформенность: механизм COM доступен только на серверах под управлением Windows. Для универсальной работы на серверах Linux рекомендуется использовать HTTP-сервисы и обмен данными в формате JSON.
← На главную