При разработке на платформе 1С:Предприятие 8.3, особенно при работе с управляемыми формами, мы часто сталкиваемся с необходимостью передавать данные из различных элементов формы в запросы к базе данных. Одной из таких ситуаций является формирование массива значений из реквизита формы типа ДеревоЗначений (или ДанныеФормыДерево) и использование этого массива в качестве параметра для запроса.
Однако, как показывает практика, нередко запрос возвращается пустым, как будто переданный параметр не отработал. Давайте разберем, почему так происходит и как правильно организовать передачу данных из дерева значений в запрос.
Исходная ситуация, описанная в нашем случае, выглядит следующим образом:
ДеревоЗначений (или ДанныеФормыДерево).СтрокаДерева = МоеДерево.ПолучитьЭлементы;
Массив = Новый Массив;
Для каждого Строка Из СтрокаДерева Цикл
Массив.Добавить(Строка.Номенклатура);
КонецЦикла;
В (&массив).Мы видим, что проблема заключается в том, что массив, передаваемый в запрос, либо пуст, либо содержит не те значения, которые ожидаются. Разберем основные причины, по которым это может происходить, и предложим пути решения.
Одна из наиболее частых причин пустых результатов запроса при работе с ДеревомЗначений на форме заключается в том, что мы извлекаем данные только с первого уровня иерархии. Если ваше ДеревоЗначений на форме имеет вложенные уровни (то есть, строки имеют подчиненные строки), то метод ПолучитьЭлементы() для реквизита формы типа ДанныеФормыДерево (который возвращает ДанныеФормыКоллекцияЭлементовДерева) по умолчанию или при простом цикле обрабатывает только элементы верхнего уровня.
Суть проблемы: Если нужная номенклатура находится на втором, третьем или более глубоком уровне иерархии дерева, она просто не попадет в наш массив.
Решение: Рекурсивный обход ДереваЗначений
Для того чтобы собрать все необходимые значения из многоуровневого дерева, нам потребуется рекурсивный обход. Такая процедура будет итерироваться по коллекции строк текущего уровня дерева или строки дерева. Если у текущей строки есть подчиненные строки (то есть, Строка.Строки не пуста), процедура должна вызывать сама себя для этой подчиненной коллекции.
Прежде чем обходить дерево, важно правильно получить его на сервере. Реквизит формы типа ДанныеФормыДерево — это объект формы, который необходимо преобразовать в прикладной объект ДеревоЗначений для полноценной работы с ним на сервере. Для этого мы используем функцию РеквизитФормыВЗначение().
Давайте рассмотрим подробный пример кода, который демонстрирует, как правильно получить ДеревоЗначений с формы и рекурсивно обойти его для сбора всех ссылок на номенклатуру:
// В Модуле формы на управляемой форме
&НаСервере
Процедура ОбработатьДеревоИЗапросНаСервере()
// 1. Получаем объект ДеревоЗначений из реквизита формы.
// Если "МоеДерево" - это реквизит формы с типом ДанныеФормыДерево,
// для работы на сервере с ним как с прикладным объектом ДеревоЗначений
// необходимо использовать функцию РеквизитФормыВЗначение().
// Это обеспечивает передачу всего объекта со всеми уровнями иерархии.
ДеревоЗначенийНаСервере = РеквизитФормыВЗначение("МоеДерево");
// Инициализируем массив для хранения собранных ссылок номенклатуры.
МассивСсылокНоменклатуры = Новый Массив;
// 2. Вызываем рекурсивную процедуру для обхода всех строк дерева,
// начиная с верхнего уровня.
ОбходДереваИДобавлениеВМассив(ДеревоЗначенийНаСервере.Строки, МассивСсылокНоменклатуры);
// 3. Формируем и выполняем запрос с собранным массивом ссылок.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка,
| Номенклатура.Наименование,
| Номенклатура.Количество
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&МассивСсылок)"; // Параметр запроса ожидает массив ссылок
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылокНоменклатуры);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Сообщить("Результаты запроса:");
Пока Выборка.Следующий() Цикл
Сообщить(" Найдена номенклатура: " + Выборка.Наименование + ", Количество: " + Выборка.Количество);
КонецЦикла;
КонецПроцедуры
// Рекурсивная процедура для обхода ДереваЗначений и сбора ссылок номенклатуры.
// Она принимает коллекцию строк текущего уровня дерева и массив,
// куда будут добавляться найденные ссылки.
&НаСервере
Процедура ОбходДереваИДобавлениеВМассив(КоллекцияСтрокДерева, МассивДляСбораСсылок)
Для Каждого СтрокаДерева Из КоллекцияСтрокДерева Цикл
// Проверяем, существует ли колонка "Номенклатура" в текущей строке дерева
// и содержит ли она ссылку на Справочник.Номенклатура.
Если СтрокаДерева.Свойство("Номенклатура") Тогда
// КРИТИЧЕСКИ ВАЖНО: убедиться, что СтрокаДерева.Номенклатура - это именно Ссылка,
// а не строка, объект ДанныеФормыЭлементДерева или иной тип.
Если ТипЗнч(СтрокаДерева.Номенклатура) = Тип("СправочникСсылка.Номенклатура") Тогда
МассивДляСбораСсылок.Добавить(СтрокаДерева.Номенклатура);
Иначе
// Если тип не соответствует ожидаемой ссылке (например, это строка с наименованием),
// можно попытаться получить ссылку из этого значения.
// Это полезно, если в дереве хранятся не сами ссылки, а их строковые представления.
Попытка
Если ТипЗнч(СтрокаДерева.Номенклатура) = Тип("Строка") Тогда
СсылкаПоНаименованию = Справочники.Номенклатура.НайтиПоНаименованию(СтрокаДерева.Номенклатура);
Если СсылкаПоНаименованию.Пустая() = Ложь Тогда
МассивДляСбораСсылок.Добавить(СсылкаПоНаименованию);
КонецЕсли;
КонецЕсли;
Исключение
// В случае ошибки при попытке получить ссылку из строки,
// мы можем пропустить это значение или записать в журнал.
Сообщить("Не удалось получить ссылку для значения номенклатуры: " + СтрокаДерева.Номенклатура, СтатусСообщения.Внимание);
КонецПопытки;
КонецЕсли;
КонецЕсли;
// Если у текущей строки есть дочерние строки, рекурсивно вызываем процедуру
// для их обхода. Это позволяет пройти по всем уровням иерархии дерева.
Если СтрокаДерева.Строки.Количество() > 0 Тогда
ОбходДереваИДобавлениеВМассив(СтрокаДерева.Строки, МассивДляСбораСсылок);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Даже если мы успешно обошли все уровни дерева и собрали данные, запрос все равно может возвращаться пустым, если в массив попали значения, тип которых не соответствует тому, что ожидает запрос. Обсуждение на форуме верно указывает на эту проблему.
Суть проблемы: Запрос, использующий оператор В (&Массив), ожидает массив значений того же типа, что и поле в базе данных, по которому выполняется отбор. В нашем случае, если мы выбираем Номенклатура.Ссылка, то в массиве должны быть объекты типа СправочникСсылка.Номенклатура.
Если в массив будут добавлены объекты ДанныеФормыЭлементДерева (что является ошибочным, так как это элемент формы, а не прикладная ссылка), строковое представление ссылки (например, "Номенклатура 1"), или любой другой объект, который не является прямой ссылкой на справочник, запрос не сможет правильно выполнить отбор. В лучшем случае он вернет пустой результат, в худшем – вызовет ошибку "Тип не может быть выбран в запросе".
Решение: Добавлять в массив именно ссылки
Мы должны гарантировать, что при добавлении в массив, предназначенный для параметра запроса, мы извлекаем из строки дерева именно объект ссылки на номенклатуру (СправочникСсылка.Номенклатура). Пример выше уже включает эту проверку:
// ... внутри процедуры ОбходДереваИДобавлениеВМассив
Если СтрокаДерева.Свойство("Номенклатура") Тогда
Если ТипЗнч(СтрокаДерева.Номенклатура) = Тип("СправочникСсылка.Номенклатура") Тогда
МассивДляСбораСсылок.Добавить(СтрокаДерева.Номенклатура);
Иначе
// Дополнительная логика, если в колонке хранится что-то другое, но можно получить ссылку
// Например, если там строка с наименованием, можно попробовать найти по наименованию
// ...
КонецЕсли;
КонецЕсли;
Эта проверка является критически важной. Она гарантирует, что в массив попадут только корректные ссылки, которые запрос сможет обработать.
Если вы программно формируете ДеревоЗначений, крайне важно явно указывать типы колонок. Это обеспечивает правильную работу с данными и их корректную передачу, предотвращая возможные ошибки несоответствия типов при последующих операциях, включая передачу в запросы.
Рассмотрим пример создания ДереваЗначений с явным указанием типов колонок:
// Пример создания ДереваЗначений программно с определением типов колонок
// Это гарантирует, что в колонке "Номенклатура" всегда будет СправочникСсылка.Номенклатура
// при корректном заполнении.
Функция СоздатьДеревоЗначенийСТипами()
НовоеДерево = Новый ДеревоЗначений;
// Добавляем колонку "Номенклатура" и явно указываем ее тип.
НовоеДерево.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
НовоеДерево.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
НовоеДерево.Колонки.Добавить("Комментарий", Новый ОписаниеТипов("Строка"));
// Добавление строк первого уровня
Строка1 = НовоеДерево.Строки.Добавить();
// Присваиваем ссылку на элемент справочника
Строка1.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию("Товар 1");
Строка1.Количество = 10;
Строка1.Комментарий = "Основной товар";
Строка2 = НовоеДерево.Строки.Добавить();
Строка2.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию("Товар 2");
Строка2.Количество = 5;
Строка2.Комментарий = "Запасной товар";
// Добавление дочерних строк
ДочерняяСтрока1 = Строка1.Строки.Добавить();
ДочерняяСтрока1.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию("Комплект к Товару 1");
ДочерняяСтрока1.Количество = 2;
ДочерняяСтрока1.Комментарий = "Комплектующие";
Возврат НовоеДерево;
КонецФункции
Когда мы присваиваем значение колонке Строка1.Номенклатура, платформа автоматически проверяет соответствие типа и, если тип не совпадает, может попытаться привести его или выдать ошибку, что помогает выявить проблему на ранних этапах.
В некоторых случаях, если данные для ДереваЗначений изначально берутся из базы данных, более эффективным может быть формирование дерева непосредственно на сервере с помощью запроса. Платформа 1С позволяет строить иерархические структуры средствами языка запросов.
Для этого мы можем использовать в тексте запроса иерархические итоги (ИТОГИ ПО ... ТОЛЬКО ИЕРАРХИЯ) и затем выгрузить результат запроса в ДеревоЗначений с помощью метода Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией).
Полученное таким образом ДеревоЗначений затем можно передать на клиентскую форму функцией ЗначениеВРеквизитФормы(). Этот подход удобен, если иерархическая структура данных уже существует в базе, и нет необходимости собирать ее по частям на клиенте.
Рассмотрим пример такого запроса:
// Пример формирования ДереваЗначений запросом с иерархией из Справочника.Номенклатура
&НаСервере
Функция ПолучитьДеревоНоменклатурыИзЗапроса()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Родитель КАК Родитель,
| Номенклатура.Ссылка КАК НоменклатураСсылка,
| Номенклатура.Наименование КАК НоменклатураНаименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ИТОГИ ПО
| Родитель ИЕРАРХИЯ"; // Используем иерархические итоги для построения дерева
РезультатЗапроса = Запрос.Выполнить();
// Выгружаем результат запроса в ДеревоЗначений, используя обход по группировкам с иерархией.
ДеревоИзЗапроса = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
// ДеревоИзЗапроса теперь содержит иерархию номенклатуры,
// где группировки соответствуют родителям, а детальные записи - элементам.
// Колонки дерева будут содержать данные из запроса.
Возврат ДеревоИзЗапроса;
КонецФункции
После получения такого ДереваЗначений на сервере, мы можем передать его на клиентскую форму в реквизит формы с типом ДанныеФормыДерево:
&НаКлиенте
Процедура ЗагрузитьДеревоНаФорму()
ДеревоНаСервере = ПолучитьДеревоНоменклатурыИзЗапроса(); // Вызов серверной функции
ЗначениеВРеквизитФормы(ДеревоНаСервере, "МоеДерево"); // Передаем на клиент
КонецПроцедуры
Этот подход позволяет снизить нагрузку на клиент и обеспечить более производительное формирование иерархических данных.
Хотя рекурсия является стандартным и элегантным подходом для работы с иерархическими структурами, мы должны помнить, что каждый вызов процедуры или функции требует выделения памяти. Для очень больших и глубоких деревьев это теоретически может сказаться на производительности или даже привести к переполнению стека вызовов, хотя в большинстве случаев для ДеревьевЗначений, используемых в интерфейсе управляемой формы, это не является критичным.
Если мы работаем с очень объемными данными, стоит рассмотреть альтернативные, итеративные подходы или заранее оптимизировать структуру данных, чтобы избежать чрезмерной рекурсии.
Итак, чтобы запрос не возвращался пустым при передаче массива из ДереваЗначений в качестве параметра, мы должны выполнить несколько ключевых шагов:
ДеревоЗначений является реквизитом управляемой формы (типа ДанныеФормыДерево), используем РеквизитФормыВЗначение() для его преобразования в прикладной объект ДеревоЗначений на сервере.СправочникСсылка.Номенклатура), а не элементы дерева, строковые представления или другие объекты. Используем ТипЗнч() для проверки и при необходимости получаем ссылку из другого типа данных (например, из строки по наименованию).ДереваЗначений используем Новый ОписаниеТипов() для колонок, чтобы обеспечить строгую типизацию данных.ДереваЗначений напрямую запросом с иерархическими итогами на сервере, а затем передаче его на клиент.Придерживаясь этих рекомендаций, мы сможем эффективно работать с ДеревомЗначений и корректно передавать его данные в запросы, избегая ошибок и пустых результатов.