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