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