Как правильно хранить и выстраивать дерево документов в 1С: стоит ли использовать ХранилищеЗначения в регистре сведений?

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

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

На первый взгляд самым простым решением кажется поместить объект ДеревоЗначений в ХранилищеЗначения и записать его в ресурс или реквизит регистра сведений. Рассмотрим подробнее, насколько оправдан такой подход, с какими проблемами можно столкнуться при его реализации и какие архитектурные альтернативы существуют.

Сохранение ДереваЗначений в ХранилищеЗначения: как это работает и где кроются риски

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

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


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

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

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

  1. Потеря ссылочной целостности: Внутри двоичных данных ХранилищеЗначения ссылки на документы сохраняются в виде сериализованных идентификаторов. Если один из документов в базе будет помечен на удаление и физически удален, при распаковке дерева ссылка превратится в «Объект не найден». Стандартный механизм «Поиск ссылок на объекты» не сможет обнаружить эти связи внутри бинарного поля.
  2. Невозможность выполнения запросов: Язык запросов 1С не умеет обращаться к содержимому полей типа ХранилищеЗначения. Вы не сможете сделать отбор, сортировку или получить аналитику по сохраненным документам средствами базы данных без полной считки записи и разархивации объекта в оперативную память.
  3. Избыточная нагрузка на СУБД и сеть: Считывание и передача больших сериализованных структур при каждом обращении к регистру увеличивает трафик между сервером 1С и СУБД.

Альтернатива 1: Динамическое построение дерева по документам-основаниям

Наиболее правильным и универсальным решением является отказ от сохранения физического дерева. Если документы в базе содержат реквизиты связи (например, ДокументОснование или родительские ссылки), дерево выстраивается динамически при открытии обработки.

Разберем по шагам, как получить иерархию связей с помощью запроса и построить ДеревоЗначений в памяти:


&НаСервере
Функция СформироватьДеревоПоОснованиям(ВыбраннаяДата)
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Реализация.Ссылка КАК Документ,
        |	Реализация.ДокументОснование КАК Родитель,
        |	Реализация.СуммаДокумента КАК Сумма
        |ИЗ
        |	Документ.РеализацияТоваровУслуг КАК Реализация
        |ГДЕ
        |	Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
        
    Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ВыбраннаяДата));
    Запрос.УстановитьПараметр("КонецПериода", КонецДня(ВыбраннаяДата));
    
    // Выгружаем результат запроса с обходом по иерархии
    Возврат Запрос.Выполнить().Выгрузить(ВыгрузкаЗапросаДеревом.Иерархия);
КонецФункции

Этот способ гарантрует актуальность ссылок, сохраняет целостность данных и работает значительно быстрее за счет оптимизации запросов на стороне СУБД.

Альтернатива 2: Нормализованное (плоское) хранение связей в регистре сведений

Если цепочка созданных обработкой документов не имеет прямого реквизита ДокументОснование, лучше создать специальный регистр сведений с плоской структурой измерений:

  1. Измерение: ПериодСеанса (Тип: Дата или Ссылка на документ-контейнер/настройку).
  2. Измерение: РодительскийОбъект (Тип: ОпределяемыйТип.ДокументыОснования).
  3. Измерение: СозданныйДокумент (Тип: ОпределяемыйТип.СоздаваемыеДокументы).
  4. Ресурсы/Реквизиты: Пользователь, УровеньВложенности.

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

Альтернатива 3: Сохранение настроек в ХранилищеСистемныхНастроек

Если дерево документов представляет собой персональные временные данные конкретного пользователя, которые не требуется заносить в физические таблицы базы данных, можно воспользоваться платформенным механизмом сохранения настроек или методом ЗначениеВФайл() / XML-сериализацией.

Посмотрим на пример использования ХранилищеОбщихНастроек:


// Сохранение структуры настроек пользователя
ХранилищеОбщихНастроек.Сохранить("ОбработкаСозданияДокументов", "ДеревоСеанса", ДеревоОбъект);

// Восстановление структуры настроек
ДеревоОбъект = ХранилищеОбщихНастроек.Загрузить("ОбработкаСозданияДокументов", "ДеревоСеанса");

Резюме и рекомендации

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

← На главную