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