Работа с деревьями значений в 1С:Предприятие 8 на стороне сервера часто вызывает затруднения у начинающих разработчиков. Одной из самых коварных задач является удаление строк, особенно если дерево имеет сложную иерархическую структуру. Типичная ошибка, с которой сталкиваются программисты: "Выполнение операции невозможно, т.к. строка была удалена". Рассмотрим подробно, почему это происходит и как реализовать удаление правильно и эффективно.
Разберем ситуацию: вы используете цикл Для каждого для обхода строк дерева. Внутри цикла, когда условие выполняется, вы вызываете метод Удалить(). В этот момент коллекция строк, по которой идет итератор, мгновенно изменяется — индексы смещаются. Итератор 1С пытается перейти к следующему элементу по старому индексу или ссылке, но структура уже нарушена. Это и приводит к аварийному завершению работы кода.
Проанализируем основные подходы к решению этой задачи, от классических до наиболее оптимизированных.
Если нам нужно удалить строки по конкретному значению колонки во всей иерархии дерева, удобнее всего использовать метод НайтиСтроки(). Этот метод позволяет искать данные сразу на всех уровнях вложенности, если указать соответствующий параметр.
Рассмотрим пример кода, который часто применяется на практике:
&НаСервере
Процедура УдалитьСтрокиПоИсточникуНаСервере()
// 1. Конвертируем данные формы в объект "ДеревоЗначений"
ДеревоОбъект = РеквизитФормыВЗначение("ДеревоРасчетыРезерваОтпусков");
// 2. Ищем все строки по условию. Параметр "Истина" включает поиск по всей иерархии
ПараметрыОтбора = Новый Структура("ИсточникДоходов", Справочники.ИсточникиДоходов.Зарплата);
МассивСтрок = ДеревоОбъект.Строки.НайтиСтроки(ПараметрыОтбора, Истина);
// 3. Удаляем найденные строки
Для Каждого ТекущаяСтрока Из МассивСтрок Цикл
// Важный нюанс: строку нужно удалять из коллекции ее родителя
Если ТекущаяСтрока.Родитель = Неопределено Тогда
ДеревоОбъект.Строки.Удалить(ТекущаяСтрока);
Иначе
ТекущаяСтрока.Родитель.Строки.Удалить(ТекущаяСтрока);
КонецЕсли;
КонецЦикла;
// 4. Возвращаем измененный объект обратно в реквизит формы
ЗначениеВРеквизитФормы(ДеревоОбъект, "ДеревоРасчетыРезерваОтпусков");
КонецПроцедуры
В этом примере мы сначала собираем все подходящие строки в массив. Поскольку массив является независимой коллекцией ссылок, удаление строк из дерева не мешает циклу продолжать обход массива. Однако стоит помнить: если вы удалили "родительскую" строку, а затем пытаетесь удалить ее "дочернюю" (которая уже исчезла вместе с родителем), может возникнуть исключение. Поэтому в коде выше рекомендуется использовать блок Попытка...Исключение или проверять существование родителя.
Если вы предпочитаете рекурсивный обход или работаете только с определенным уровнем дерева, самый надежный способ — это цикл "с конца в начало". В этом случае смещение индексов происходит в той части коллекции, которую мы уже обработали, и это не влияет на текущий шаг.
Выясним, как это выглядит в коде:
&НаСервере
Процедура ОбработатьУровеньДерева(КоллекцияСтрок)
// Получаем количество строк на текущем уровне
Счетчик = КоллекцияСтрок.Количество() - 1;
// Идем от последней строки к первой
Пока Счетчик >= 0 Цикл
ТекущаяСтрока = КоллекцияСтрок[Счетчик];
// Сначала заходим в рекурсию для обработки дочерних элементов (снизу вверх)
Если ТекущаяСтрока.Строки.Количество() > 0 Тогда
ОбработатьУровеньДерева(ТекущаяСтрока.Строки);
КонецЕсли;
// Теперь проверяем условие удаления для текущей строки
Если ТекущаяСтрока.ИсточникДоходов = Справочники.ИсточникиДоходов.Зарплата Тогда
КоллекцияСтрок.Удалить(Счетчик);
КонецЕсли;
Счетчик = Счетчик - 1;
КонецЦикла;
КонецПроцедуры
Этот алгоритм гарантирует, что мы сначала удалим все ненужные вложенные элементы, а затем перейдем к их родителям. Такой подход исключает любые ошибки, связанные с неопределенными состояниями объектов.
Часто программисты забывают, что методы РеквизитФормыВЗначение и ЗначениеВРеквизитФормы являются достаточно ресурсоемкими. Они копируют все данные дерева из памяти формы в объект и обратно. Кроме того, после вызова ЗначениеВРеквизитФормы в интерфейсе пользователя все развернутые узлы дерева "схлопываются", что неудобно для пользователя.
Рассмотрим, как манипулировать строками напрямую через ДанныеФормыДерево. Для этого используется метод ПолучитьЭлементы().
&НаСервере
Процедура УдалитьНапрямуюВРеквизите()
// Работаем напрямую с реквизитом формы "ДеревоРасчеты"
КоллекцияВерхнегоУровня = ДеревоРасчетыРезерваОтпусков.ПолучитьЭлементы();
УдалитьРекурсивно(КоллекцияВерхнегоУровня);
КонецПроцедуры
&НаСервере
Процедура УдалитьРекурсивно(Коллекция)
Индекс = Коллекция.Количество() - 1;
Пока Индекс >= 0 Цикл
Элемент = Коллекция[Индекс];
// Рекурсия для подчиненных
УдалитьРекурсивно(Элемент.ПолучитьЭлементы());
// Проверка условия
Если Элемент.ИсточникДоходов = Справочники.ИсточникиДоходов.Зарплата Тогда
Коллекция.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
КонецПроцедуры
Преимущества этого метода:
Проанализировав ситуацию, можно выделить несколько критически важных моментов:
ДеревоЗначений, не забудьте вызвать ЗначениеВРеквизитФормы, иначе изменения не отобразятся на экране пользователя.Удалить() у коллекции ДеревоЗначений.Строки работает только для корневых узлов. Для удаления вложенной строки всегда обращайтесь к коллекции Родитель.Строки.ПолучитьИдентификатор()), чтобы точно найти нужный объект после обновления контекста.Использование любого из предложенных методов — будь то обратный цикл или использование массива ссылок — позволит вам навсегда забыть об ошибке "строка была удалена" и сделать ваш код более надежным.