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