Запись данных в ХранилищеЗначений: от реквизита до файла
Содержание
Чтобы сохранить значение в хранилище значений 1С, создайте объект Новый ХранилищеЗначений(Значение, Новый СжатиеДанных(9)) и присвойте его реквизиту типа «ХранилищеЗначения», а затем запишите объект базы. Делается это только на сервере: в тонком и веб-клиенте этого типа нет, поэтому с формы данные идут через временное хранилище.
Типичная ситуация: в справочник «Номенклатура» нужно положить фото товара, а в документ — скан договора или выгрузку таблицы для повторной обработки. Ниже — рабочий код для каждого шага, от добавления реквизита в конфигураторе до чтения данных обратно. Механизм общий для платформы 8.3 и не зависит от конфигурации: БП, ЗУП, УТ или своя разработка.
Что такое ХранилищеЗначений и где оно живёт
ХранилищеЗначений — это контейнер, который упаковывает почти любое значение 1С в двоичный вид и позволяет записать его в реквизит объекта базы. Внутри может лежать картинка, файл, ТаблицаЗначений, Структура, Массив, табличный документ.
Ключевые свойства типа:
- 📦 Упаковка целиком. Значение сериализуется при создании хранилища. Менять его «внутри» нельзя: получили, изменили, упаковали заново.
- 🖥️ Доступность. Тип есть на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте его нет.
- 🔍 Без запросов по содержимому. По реквизиту-хранилищу нельзя отбирать, сортировать и группировать. Его можно только выбрать в запросе и прочитать.
- 🗜️ Сжатие по желанию. Второй параметр конструктора задаёт уровень сжатия от 0 до 9.
Как добавить реквизит в конфигураторе
- Откройте объект метаданных (справочник, документ, регистр сведений ).
- На вкладке «Данные» добавьте реквизит, например
ФотоилиДанныеФайла. - В поле «Тип» выберите
ХранилищеЗначения. - Обновите конфигурацию базы данных (
F7).
Для типовой конфигурации реквизит лучше добавить в расширение, а не в основную конфигурацию: так он переживёт обновления без ручного сравнения-объединения. Сами расширения появились в платформе 8.3.6, добавление своих реквизитов к заимствованным объектам — в более поздних версиях 8.3.
Программная запись в хранилище на сервере
Базовая схема одна для любого типа данных: получить значение → упаковать → присвоить реквизиту → записать объект.
Пример 1. Файл с диска сервера в справочник
&НаСервере
Процедура СохранитьФайлВТовар(СсылкаТовар, ПутьКФайлу)
ДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
ТоварОбъект = СсылкаТовар.ПолучитьОбъект();
ТоварОбъект.Фото = Новый ХранилищеЗначений(ДанныеФайла, Новый СжатиеДанных(9));
ТоварОбъект.Записать();
КонецПроцедуры
Путь здесь — путь на машине, где работает сервер 1С, а не на компьютере пользователя. Для клиент-серверной базы это частая причина ошибки «файл не найден».
Пример 2. Таблица значений в регистр сведений
&НаСервере
Процедура СохранитьСнимокОстатков(Ключ, ТаблицаОстатков)
МенеджерЗаписи = РегистрыСведений.СнимкиДанных.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Ключ = Ключ;
МенеджерЗаписи.Дата = ТекущаяДатаСеанса();
МенеджерЗаписи.Данные = Новый ХранилищеЗначений(ТаблицаОстатков, Новый СжатиеДанных(9));
МенеджерЗаписи.Записать(Истина);
КонецПроцедуры
Параметр Истина в Записать() заменяет существующую запись с тем же ключом. Регистр СнимкиДанных с измерением Ключ и ресурсами Дата и Данные нужно создать самому — в типовых его нет.
Пример 3. Структура настроек
Настройки = Новый Структура;
Настройки.Вставить("Склад", СкладПоУмолчанию);
Настройки.Вставить("ПорогОстатка", 10);
Настройки.Вставить("ДатаНачала", НачалоМесяца(ТекущаяДатаСеанса()));
ОбъектНастроек.Параметры = Новый ХранилищеЗначений(Настройки);
ОбъектНастроек.Записать();
Для мелкой структуры сжатие не нужно: выигрыш в байтах, а время на упаковку всё равно тратится.
Сохранение с формы: файл от пользователя
Пользователь выбирает файл у себя на компьютере, а записать его нужно в базу на сервере. Прямо передать ХранилищеЗначений с клиента нельзя, поэтому цепочка такая:
- Клиент передаёт файл во временное хранилище и получает адрес — строку вида
e1cib/tempstorage/…. - Адрес сохраняется в реквизите формы типа «Строка».
- При записи объекта сервер берёт данные по адресу и упаковывает в реквизит.
Код на клиенте: выбор и передача файла
&НаКлиенте
Процедура ЗагрузитьФото(Команда)
Оповещение = Новый ОписаниеОповещения("ЗагрузитьФотоЗавершение", ЭтотОбъект);
НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ЗагрузитьФотоЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Не Результат Тогда
Возврат; // пользователь закрыл диалог
КонецЕсли;
АдресФото = Адрес;
Модифицированность = Истина;
КонецПроцедуры
Четвёртый параметр Истина открывает диалог выбора файла. Пятый — УникальныйИдентификатор формы: данные проживут во временном хранилище, пока форма открыта. В новых версиях платформы есть и метод НачатьПомещениеФайлаНаСервер с более подробным описанием результата — логика та же.
Код на сервере: упаковка при записи
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресФото) Тогда
Данные = ПолучитьИзВременногоХранилища(АдресФото);
ТекущийОбъект.Фото = Новый ХранилищеЗначений(Данные, Новый СжатиеДанных(9));
КонецЕсли;
КонецПроцедуры
Обработчик ПередЗаписьюНаСервере получает именно ТекущийОбъект — объект базы, а не данные формы. Реквизит-хранилище в данных формы недоступен, писать его можно только так.
Показ картинки на форме
Добавьте на форму поле картинки, привязанное к строковому реквизиту АдресФото. При открытии существующего элемента заполните адрес навигационной ссылкой на реквизит:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
АдресФото = ПолучитьНавигационнуюСсылку(Объект.Ссылка, "Фото");
КонецЕсли;
КонецПроцедуры
Платформа сама достанет картинку из хранилища по этой ссылке. В обработчике ПередЗаписьюНаСервере проверка ЭтоАдресВременногоХранилища отсеет навигационную ссылку, и старое фото не перезапишется лишний раз.
Временное хранилище: когда оно вместо постоянного
Временное хранилище не пишет данные в таблицы базы. Это область памяти сеанса для передачи данных между клиентом и сервером и между вызовами сервера.
| Задача | Что использовать | Сколько живут данные |
|---|---|---|
| Хранить файл у элемента справочника | Реквизит типа ХранилищеЗначения | Пока не перезаписали или не удалили объект |
| Передать файл с клиента на сервер | ПоместитьВоВременноеХранилище, НачатьПомещениеФайла | До закрытия формы, если передан её идентификатор |
| Вернуть результат фонового задания | ПоместитьВоВременноеХранилище в задании, чтение по адресу на клиенте | До закрытия формы-владельца |
| Отдать пользователю файл на скачивание | ПоместитьВоВременноеХранилище + ПолучитьФайл на клиенте | Короткое время, до завершения передачи |
Основные методы глобального контекста:
ПоместитьВоВременноеХранилище(Данные, Адрес)— кладёт значение и возвращает адрес. Вторым параметром передаютУникальныйИдентификаторформы или уже существующий адрес.ПолучитьИзВременногоХранилища(Адрес)— возвращает значение.УдалитьИзВременногоХранилища(Адрес)— освобождает память, не дожидаясь закрытия формы.ЭтоАдресВременногоХранилища(Адрес)— проверка, что строка действительно адрес.
Без идентификатора формы данные во временном хранилище живут недолго — до конца вызова сервера. Рассчитывать на них в следующем действии пользователя нельзя: адрес вернёт Неопределено.
Большие файлы во временном хранилище занимают память рабочего процесса сервера. Если форма загружает десятки мегабайт, удаляйте данные методом УдалитьИзВременногоХранилища сразу после записи.
Что можно и что нельзя упаковать
В хранилище кладут значения, которые платформа умеет сериализовать. Объекты, существующие только в памяти сеанса, упаковать не получится — будет ошибка выполнения при вызове конструктора.
| Можно сохранить | Нельзя сохранить |
|---|---|
| Строка, Число, Дата, Булево | Запрос, ПостроительЗапроса |
| Ссылки на объекты базы | COMОбъект |
ДвоичныеДанные, Картинка | Форма и её элементы |
ТаблицаЗначений, ДеревоЗначений | ЧтениеXML, ЗаписьXML, потоки |
Массив, Структура, Соответствие | Объекты внешних компонент |
ТабличныйДокумент | Открытые файлы и соединения |
Если в Структуре есть хотя бы одно несериализуемое свойство, не упакуется вся структура. Ищите виновника по очереди: упакуйте каждое свойство отдельно в Попытка … Исключение.
Защита записи от ошибок
Попытка
ТекущийОбъект.ДанныеФайла = Новый ХранилищеЗначений(Данные, Новый СжатиеДанных(9));
Исключение
ЗаписьЖурналаРегистрации("СохранениеВХранилище",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Отказ = Истина;
КонецПопытки;
С Отказ = Истина объект не запишется наполовину, а причина останется в журнале регистрации.
Чтение из хранилища и восстановление данных
Обратная операция — метод Получить(). Он возвращает исходное значение того же типа, что упаковали.
&НаСервере
Функция ПрочитатьСнимок(Ключ)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СнимкиДанных.Данные КАК Данные
|ИЗ
| РегистрСведений.СнимкиДанных КАК СнимкиДанных
|ГДЕ
| СнимкиДанных.Ключ = &Ключ";
Запрос.УстановитьПараметр("Ключ", Ключ);
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
Возврат Неопределено;
КонецЕсли;
Значение = Выборка.Данные.Получить();
Если ТипЗнч(Значение) <> Тип("ТаблицаЗначений") Тогда
Возврат Неопределено;
КонецЕсли;
Возврат Значение;
КонецФункции
Две проверки обязательны. Пустой реквизит возвращает Неопределено. А проверка типа защищает от старых записей, где когда-то лежало что-то другое.
Как вернуть данные в рабочие объекты
- 📋 Табличная часть.
Объект.Товары.Загрузить(Таблица)на сервере, если колонки совпадают по именам. - 🖼️ Картинка для формы.
ПоместитьВоВременноеХранилище(Значение, УникальныйИдентификатор)и адрес в поле картинки. - 💾 Файл пользователю. Положите
ДвоичныеДанныево временное хранилище, на клиенте вызовитеНачатьПолучениеФайловилиПолучитьФайл. - 🔁 Восстановление предыдущей версии. Если хранили снимки по датам, прочитайте нужный и загрузите обратно в объект — так делают простой откат настроек.
Как не утяжелить базу: практика
Хранилище удобно, но легко превращает справочник в мешок с мегабайтами. Вот что помогает.
Выносите файлы в отдельный объект
При чтении объекта платформа читает все его реквизиты, включая хранилище. Если в каждом элементе «Номенклатуры» лежит фото на 2 МБ, то любое Ссылка.ПолучитьОбъект() тянет эти 2 МБ.
Надёжнее держать файлы в отдельном справочнике или регистре сведений с ключом-ссылкой на владельца. В конфигурациях на БСП так и устроено: подсистема «Присоединенные файлы» хранит файлы отдельно от владельца, в базе или на томах на диске.
Подбирайте сжатие под данные
| Данные | Эффект от СжатиеДанных(9) |
|---|---|
| Текст, XML, JSON, таблицы значений | Заметный, размер падает в разы |
| JPEG, PNG, ZIP, PDF со сканами | Почти никакого, файлы уже сжаты |
| Мелкие структуры настроек | Не нужен |
Не выбирайте хранилище в массовых запросах
Поле-хранилище в списке выбора запроса передаёт содержимое по сети. В отчёт по 10 000 товарам оно может добавить сотни мегабайт. Выбирайте его только там, где содержимое действительно нужно.
Для паролей и токенов — не обычный реквизит
Пароли к сервисам в хранилище обычного реквизита видны любому, у кого есть право чтения объекта. В БСП для этого есть безопасное хранилище: ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище(Владелец, Данные, Ключ) и ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища(Владелец, Ключ). Доступ к нему закрыт для обычных пользователей.
Пользовательские настройки — в хранилище настроек
Если нужно запомнить выбор пользователя (последний склад, фильтр), реквизит не нужен. Используйте ХранилищеОбщихНастроек.Сохранить(КлючОбъекта, КлючНастроек, Значение) и ХранилищеОбщихНастроек.Загрузить(КлючОбъекта, КлючНастроек). Настройки привязываются к пользователю автоматически.