Как вывести полное наименование иерархического справочника в 1С

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

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

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

Способ 1. Использование события модуля менеджера (Быстрый, но опасный метод)

Выясним, как решить задачу в лоб с использованием механизмов платформы. Для управления текстовым представлением ссылок в платформе 1С:Предприятие 8.3 предусмотрено специальное событие — ОбработкаПолученияПредставления, которое располагается в модуле менеджера прикладного объекта.

Посмотрим на пример кода, который можно внедрить в модуль менеджера справочника Сегменты:


Процедура ОбработкаПолученияПредставления(Данные, Представление, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Сегменты.Ссылка КАК Ссылка,
        |    Сегменты.Наименование КАК Наименование
        |ИЗ
        |    Справочник.Сегменты КАК Сегменты
        |ГДЕ
        |    Сегменты.Ссылка = &Ссылка
        |ИТОГИ ПО
        |    Ссылка ТОЛЬКО ИЕРАРХИЯ";
        
    Запрос.УстановитьПараметр("Ссылка", Данные.Ссылка);
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        Если Представление = "" Тогда
            Представление = Выборка.Наименование;
        Иначе
            Представление = "" + Выборка.Наименование + " \ " + Представление;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Разберем подробнее, как работает этот механизм. Мы отключаем стандартную обработку формирования представления платформой (СтандартнаяОбработка = Ложь) и выполняем запрос к базе данных. Использование конструкции ИТОГИ ПО Ссылка ТОЛЬКО ИЕРАРХИЯ позволяет нам за один простой запрос получить всю цепочку родителей для конкретного элемента. Затем в цикле мы обходим выборку и конкатенируем наименования через разделитель.

Внимание! Хотя данный метод выглядит очень изящно и решает задачу буквально в 20 строк кода, он содержит критическую проблему производительности, известную как проблема N+1 запроса.

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

Способ 2. Архитектурно правильное решение: Денормализация данных

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

Разберем этот надежный способ по шагам:

  1. Добавим в справочник Сегменты новый реквизит ПолноеНаименование с типом Строка (длину укажем с запасом, например, 150 или 250 символов).
  2. В модуле объекта справочника Сегменты переопределим обработчик события ПередЗаписью, чтобы автоматически рассчитывать значение этого реквизита при сохранении элемента.

Посмотрим на пример реализации формирования полного пути в модуле объекта:


Процедура ПередЗаписью(Отказ)
    
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Формируем полное наименование на основе родителей
    ТекущийРодитель = Родитель;
    СтрокаПути = Наименование;
    
    Пока ЗначениеЗаполнено(ТекущийРодитель) Цикл
        СтрокаПути = СокрЛП(ТекущийРодитель.Наименование) + " / " + СтрокаПути;
        ТекущийРодитель = ТекущийРодитель.Родитель;
    КонецЦикла;
    
    ПолноеНаименование = СтрокаПути;
    
КонецПроцедуры

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

Теперь нам необходимо правильно связать сохраненное значение с механизмом представлений в модуле менеджера справочника Сегменты. Для этого переопределим две процедуры:


Процедура ОбработкаПолученияПолейПредставления(Поля, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    Поля.Добавить("ПолноеНаименование");
    
КонецПроцедуры

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

Выясним, в чем ключевое преимущество этой связки. Использование метода ОбработкаПолученияПолейПредставления указывает платформе, что при выборке ссылки из БД необходимо сразу же неявно выбирать и поле ПолноеНаименование. В результате в обработчик ОбработкаПолученияПредставления передается структура Данные, уже содержащая готовое строковое значение. Платформа компонует данные одним общим запросом, что полностью решает проблему производительности в динамических списках!

Способ 3. Кэширование данных в памяти (Альтернативный вариант)

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

Создадим общий модуль СегментыВызовСервераПовтИсп и разместим там функцию получения кэшированной структуры путей:


Функция ПолучитьТаблицуПолныхНаименований() Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Сегменты.Ссылка КАК Ссылка,
        |    Сегменты.Наименование КАК Наименование,
        |    Сегменты.Родитель КАК Родитель
        |ИЗ
        |    Справочник.Сегменты КАК Сегменты";
        
    ТаблицаДанных = Запрос.Выполнить().Выгрузить();
    
    СоответствиеПутей = Новый Соответствие;
    Для Каждого Строка Из ТаблицаДанных Цикл
        // Рекурсивно или в цикле собираем путь для каждого элемента
        Путь = Строка.Наименование;
        ТекущийРодитель = Строка.Родитель;
        Пока ЗначениеЗаполнено(ТекущийРодитель) Цикл
            НайденнаяСтрока = ТаблицаДанных.Найти(ТекущийРодитель, "Ссылка");
            Если НайденнаяСтрока <> Неопределено Тогда
                Путь = НайденнаяСтрока.Наименование + " / " + Путь;
                ТекущийРодитель = НайденнаяСтрока.Родитель;
            Иначе
                Прервать;
            EndIf;
        КонецЦикла;
        СоответствиеПутей.Вставить(Строка.Ссылка, Путь);
    КонецЦикла;
    
    Возврат СоответствиеПутей;
    
КонецФункции

Теперь в модуле менеджера справочника мы можем быстро получать строковое представление без единого обращения к базе данных во время работы обработчика:


Процедура ОбработкаПолученияПредставления(Данные, Представление, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    ТаблицаПутей = СегментыВызовСервераПовтИсп.ПолучитьТаблицуПолныхНаименований();
    
    НайденныйПуть = ТаблицаПутей.Получить(Данные.Ссылка);
    Если НайденныйПуть <> Неопределено Тогда
        Представление = НайденныйПуть;
    Иначе
        СтандартнаяОбработка = Истина;
    КонецЕсли;
    
КонецПроцедуры

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

← На главную