При работе с объектом ДеревоЗначений часто возникает задача дублирования существующей ветки. Это может потребоваться для создания шаблонов, копирования настроек или формирования сложных иерархических структур на основе уже имеющихся данных. В отличие от простых массивов, дерево требует не только копирования данных текущей строки, но и воссоздания всей структуры подчиненных элементов.
Рассмотрим подробнее, как реализовать эту задачу наиболее эффективным и «человеческим» способом, разберем программную логику и изучим различные нюансы, возникающие при работе в управляемых формах и с большими объемами данных.
Наиболее логичным и понятным способом копирования ветки дерева является использование рекурсии. Суть метода заключается в том, что мы создаем процедуру, которая копирует текущую строку и вызывает саму себя для всех дочерних строк. Проанализируем, как это выглядит на практике.
Для реализации нам потребуется процедура, принимающая два параметра: Источник (строка, которую копируем) и Приемник (коллекция, куда добавляем копию). Коллекцией-приемником может выступать как само ДеревоЗначений, так и свойство Строки любой другой строки дерева.
Разберем пример кода:
// Процедура для копирования ветки дерева
Процедура СкопироватьРекурсивно(СтрокаИсточник, КоллекцияПриемник)
// 1. Добавляем новую строку в целевую коллекцию
НоваяСтрока = КоллекцияПриемник.Добавить();
// 2. Копируем значения всех колонок из источника в новую строку
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточник);
// 3. Обходим все подчиненные строки источника
Для Каждого ПодчиненнаяСтрока Из СтрокаИсточник.Строки Цикл
// Рекурсивно вызываем эту же процедуру для каждой вложенной ветки
СкопироватьРекурсивно(ПодчиненнаяСтрока, НоваяСтрока.Строки);
КонецЦикла;
КонецПроцедуры
Почему это работает:
ЗаполнитьЗначенияСвойств автоматически сопоставляет имена колонок источника и приемника, перенося данные. Это избавляет нас от необходимости вручную перечислять все поля дерева.НоваяСтрока.Строки в качестве приемника на следующем уровне рекурсии обеспечивает правильное построение иерархии.Если ваше дерево значений является реквизитом управляемой формы (тип ДанныеФормыДерево), прямой рекурсивный обход на клиенте может быть менее производительным из-за серверных вызовов или особенностей работы с коллекциями данных формы. В этом случае мы рекомендуем следующий алгоритм:
Во-первых, если копирование происходит на сервере, лучше временно преобразовать дерево в объект ДеревоЗначений, выполнить копирование и вернуть результат обратно. Это позволит использовать все возможности объектной модели 1С.
Проанализируем шаги:
РеквизитФормыВЗначение.ЗначениеВРеквизитФормы.Посмотрим на пример реализации в контексте серверного метода формы:
&НаСервере
Процедура КопироватьВеткуНаСервере(ИдентификаторСтроки)
// Превращаем данные формы в полноценный объект ДеревоЗначений
ДеревоОбъект = РеквизитФормыВЗначение("ДеревоДанных");
// Находим нужную строку-источник по идентификатору или иным способом
СтрокаИсточник = ДеревоОбъект.Строки.Найти(ИдентификаторСтроки, "Идентификатор", Истина);
Если СтрокаИсточник <> Неопределено Тогда
// Вызываем нашу рекурсивную процедуру
// Копируем в корень того же дерева
СкопироватьРекурсивно(СтрокаИсточник, ДеревоОбъект.Строки);
КонецЕсли;
// Возвращаем измененный объект в реквизит формы
ЗначениеВРеквизитФормы(ДеревоОбъект, "ДеревоДанных");
КонецПроцедуры
Выясним причину, по которой иногда стоит использовать XML. Если структура дерева крайне сложная или нам нужно создать абсолютно независимую копию всей ветки для передачи между разными объектами, можно использовать механизм сериализации XDTO. Этот метод считается более «тяжелым», но он гарантирует полное копирование структуры «одним действием».
Однако стоит учитывать, что сериализация всей ветки дерева значений как отдельного объекта требует создания временного дерева, в которое эта ветка будет помещена перед выгрузкой в XML. Для большинства повседневных задач 1С-программиста рекурсивный метод, описанный выше, является более предпочтительным и быстрым.
При копировании элементов дерева важно не забывать про специфические колонки. Рассмотрим основные моменты, на которые стоит обратить внимание:
1. Уникальные идентификаторы (GUID). Если в вашем дереве есть колонка ID или УникальныйИдентификатор, метод ЗаполнитьЗначенияСвойств скопирует и её. В результате в дереве появятся две разные строки с одинаковыми идентификаторами, что может привести к ошибкам в логике программы. Решение: после вызова ЗаполнитьЗначенияСвойств принудительно очищайте или генерируйте новый идентификатор для НоваяСтрока.
2. Служебные колонки. Если дерево содержит вычисляемые колонки или элементы, связанные с оформлением (например, картинки строк), убедитесь, что они также корректно переносятся. Если некоторые колонки доступны только для чтения, ЗаполнитьЗначенияСвойств просто пропустит их, не вызывая ошибки.
3. Производительность. На очень больших деревьях (десятки тысяч строк) рекурсия может вызвать переполнение стека или работать медленно. В таких исключительных случаях можно рассмотреть метод выгрузки части дерева в таблицу значений с последующей загрузкой, но для типовых задач автоматизации бизнеса рекурсия — это стандарт де-факто.
4. Использование БСП. Хотя в Библиотеке стандартных подсистем нет одной универсальной функции СкопироватьВетку(), часто полезные инструменты можно найти в модулях ОбщегоНазначения. Тем не менее, написание собственной процедуры из 7-10 строк кода (как мы разобрали выше) зачастую быстрее и эффективнее, чем поиск и подключение громоздких библиотечных функций.
Таким образом, мы выяснили, что наиболее эффективный способ копирования элемента дерева значений — это небольшая рекурсивная процедура с применением метода ЗаполнитьЗначенияСвойств. Это решение гибкое, легко читаемое и простое в поддержке.