Как программно изменить родителя элемента справочника в 1С 8.3 и избежать типичных ошибок

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

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

В этой статье мы подробно разберем, как правильно изменять родителя (группу) у элементов справочника, проанализируем причины возникновения ошибок из практики реальных форумов и рассмотрим лучшие методы оптимизации производительности при работе с иерархическими структурами.

Понимание основ: Ссылка против Объекта в 1С

Прежде чем перейти к написанию кода, давайте разберем ключевую архитектурную особенность платформы «1С:Предприятие». База данных строго разделяет работу со ссылочными типами данных на два состояния:

  1. СправочникСсылка (неизменяемый тип): Это постоянный уникальный идентификатор (GUID) записи в таблице базы данных. Ссылка позволяет считывать данные (например, получить наименование элемента или узнать его текущего родителя), но через нее категорически нельзя изменять реквизиты. Попытка написать Ссылка.Родитель = НовыйРодитель; вызовет ошибку компиляции или выполнения.
  2. СправочникОбъект (изменяемый тип): Это копия записи из базы данных, загруженная в оперативную память сервера для редактирования. Только у объекта существуют методы редактирования реквизитов и метод Записать(), который переносит изменения обратно в физическую таблицу СУБД.

Для перехода от ссылки к объекту используется метод ПолучитьОбъект(). Проанализируем, почему возникает популярная ошибка «Элемент не выбран» при выполнении этого метода. Она появляется в тех случаях, когда метод ПолучитьОбъект() вызывается у пустой ссылки. Если поиск элемента в базе данных (например, по наименованию или коду) не увенчался успехом, система возвращает пустую ссылку (Справочники.Банки.ПустаяСсылка()). Попытка получить объект из пустоты приводит к аварийному завершению транзакции.

Разбор ошибочного кода с форума

Рассмотрим реальный пример неоптимального и ошибочного кода, с которого часто начинают неопытные специалисты:


Спр = Справочники.Банки;
Выборка = Спр.Выбрать();

Пока Выборка.Следующий() Цикл
    Если Спр.НайтиПоНаименованию(Выборка.Банк) <> Спр.НайтиПоНаименованию(Выборка.Родитель) Тогда
        Поиск = Спр.НайтиПоНаименованию(Выборка.Банк);
        Поиск.Родитель = Выборка.Родитель.Ссылка;
        Выборка.Записать(); // Ошибка!
    КонецЕсли;
КонецЦикла;

Давайте детально разберем, какие грубые ошибки допущены в этом фрагменте кода:

Классическое правильное решение задачи

Для исправления вышеописанных проблем мы должны получить ссылку на нужный элемент, преобразовать её в объект при помощи метода ПолучитьОбъект(), проверить заполненность данных, внести изменения в реквизит Родитель и вызвать метод Записать() непосредственно у объекта справочника.

Простейший пример безопасного изменения родителя для одного конкретного элемента выглядит следующим образом:


СсылкаНаЭлемент = Справочники.Банки.НайтиПоНаименованию("СберБанк");
СсылкаНаНовогоРодителя = Справочники.Банки.НайтиПоНаименованию("ПАО Сбербанк (Группа)");

// Обязательно проверяем, найдены ли элементы в базе данных
Если ЗначениеЗаполнено(СсылкаНаЭлемент) И ЗначениеЗаполнено(СсылкаНаНовогоРодителя) Тогда
    
    // Получаем объект для редактирования
    ОбъектБанка = СсылкаНаЭлемент.ПолучитьОбъект();
    
    // Устанавливаем нового родителя
    ОбъектБанка.Родитель = СсылкаНаНовогоРодителя;
    
    // Записываем изменения в базу данных
    Попытка
        ОбъектБанка.Записать();
        Сообщить("Родитель успешно изменен для элемента: " + СсылкаНаЭлемент);
    Исключение
        Сообщить("Не удалось записать элемент. Ошибка: " + ОписаниеОшибки());
    КонецПопытки;
    
Иначе
    Сообщить("Ошибка: Не найден изменяемый элемент или новый родитель!");
КонецЕсли;

Оптимальное решение для групповой обработки данных

Если перед нами стоит задача массово изменить родителей у множества элементов справочника (например, на основании таблицы значений на форме или по какому-то условию), использовать стандартный перебор и поиск по наименованию нельзя. Мы должны применить механизм Запросов. Для выполнения таких операций без программирования можно использовать универсальную обработку для массового изменения реквизитов объектов.

Запросы позволяют выполнить предварительную фильтрацию данных на стороне сервера СУБД и получить только те записи, которые действительно требуют изменения. Рассмотрим профессиональный шаблон кода для массового изменения родителя справочника:


// Шаг 1. Формируем запрос для получения только тех элементов, у которых родитель указан неверно
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Банки.Ссылка КАК Ссылка,
    |	Банки.БанкДляСопоставления КАК ИмяНовогоРодителя
    |ИЗ
    |	Справочник.Банки КАК Банки
    |ГДЕ
    |	Банки.Родитель.Наименование <> Банки.БанкДляСопоставления
    |	И НЕ Банки.ЭтоГруппа"; // Меняем родителей только у элементов, папки не трогаем

РезультатЗапроса = Запрос.Выполнить();

Если РезультатЗапроса.Пустой() Тогда
    Сообщить("Нет данных для обновления родительских групп.");
    Возврат;
КонецЕсли;

Выборка = РезультатЗапроса.Выбрать();

// Шаг 2. Открываем транзакцию для безопасной групповой записи
НачатьТранзакцию();

Попытка
    Пока Выборка.Следующий() Цикл
        
        // Быстро находим ссылку нового родителя в базе данных
        НовыйРодитель = Справочники.Банки.НайтиПоНаименованию(Выборка.ИмяНовогоРодителя);
        
        Если ЗначениеЗаполнено(НовыйРодитель) Тогда
            
            // Получаем объект элемента справочника
            ОбъектСпр = Выборка.Ссылка.ПолучитьОбъект();
            
            // Назначаем нового родителя
            ОбъектСпр.Родитель = НовыйРодитель;
            
            // Записываем объект
            ОбъектСпр.Записать();
            
        КонецЕсли;
        
    КонецЦикла;
    
    // Если все элементы успешно записались, фиксируем транзакцию
    ЗафиксироватьТранзакцию();
    Сообщить("Групповое изменение родителей успешно завершено!");
    
Исключение
    // В случае возникновения любой ошибки (например, блокировки базы данных), откатываем изменения
    ОтменитьТранзакцию();
    Сообщить("Произошла ошибка при групповой записи. Транзакция отменена. Ошибка: " + ОписаниеОшибки());
КонецПопытки;

Важные технические нюансы и скрытые ловушки

При программном изменении иерархии справочников разработчик должен учитывать несколько критических факторов, которые могут привести к нарушению целостности данных или аварийным отказам системы.

1. Зацикливание иерархии (Цикличность)

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

Чтобы этого избежать, перед записью необходимо использовать метод ссылки ПринадлежитЭлементу(). Он проверяет, не входит ли новый родитель в состав подчиненных элементов изменяемой группы. Пример проверки:


// Проверяем, не пытаемся ли мы перенести группу саму в себя или в свои подпапки
Если ИзменяемаяГруппаСсылка = НовыйРодительСсылка 
    Или НовыйРодительСсылка.ПринадлежитЭлементу(ИзменяемаяГруппаСсылка) Тогда
    
    Сообщить("Действие отменено! Обнаружена угроза зацикливания иерархии.");
    // Пропускаем запись этого элемента
    Продолжить; 
КонецЕсли;

2. Ограничение количества уровней иерархии

В свойствах метаданных любого иерархического справочника в конфигураторе разработчик может жестко задать свойство «Количество уровней». Если там установлено ограничение, например, 3 уровня, а вы программно пытаетесь перенести элемент в папку, которая сама находится на третьем уровне вложенности, то переносимый элемент должен будет занять четвертый уровень. При вызове метода Записать() система выдаст ошибку нарушения метаданных. Контролировать уровень вложенности можно с помощью метода ссылки Уровень().

3. Перенос элемента на самый верхний уровень («в корень»)

Иногда требуется очистить родительскую группу и перенести элемент в самый корень справочника (чтобы у него не было родителя). Для этого свойству Родитель нужно присвоить пустую ссылку этого справочника. Попытка присвоить значение Неопределено или Null приведет к ошибке несовместимости типов. Правильный код переноса в корень:


ОбъектСпр.Родитель = Справочники.Банки.ПустаяСсылка();
ОбъектСпр.Записать();

4. Использование режима загрузки данных для ускорения работы

Если вы уверены в корректности переносимых данных и хотите выполнить обработку максимально быстро, минуя все встроенные прикладные проверки в модуле справочника (процедуры ПередЗаписью, ПриЗаписи), вы можете использовать специальное свойство объекта ОбменДанными.Загрузка:


ОбъектСпр = Выборка.Ссылка.ПолучитьОбъект();
ОбъектСпр.Родитель = НовыйРодитель;

// Отключаем выполнение прикладного кода в модулях объекта и подписок на события
ОбъектСпр.ОбменДанными.Загрузка = Истина; 

ОбъектСпр.Записать();

Важное предупреждение: Используйте режим загрузки с осторожностью, так как при этом не будут отрабатывать важные бизнес-правила (например, автоматическое перерасчеты созависимых регистров сведений или проверка заполнения обязательных реквизитов).

Применяя данные подходы и учитывая технические особенности платформы 1С при работе с иерархией, вы сможете писать быстрый, безопасный и отказоустойчивый код для любых групповых обработок справочников. При необходимости избежать написания собственного кода подойдет расширение для группового редактирования элементов справочников непосредственно из списков.

← На главную