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