Тип ХранилищеЗначений: как положить в реквизит базы файл, картинку или структуру
Содержание
ХранилищеЗначений в 1С — это обёртка, которая позволяет записать в реквизит базы данных значение почти любого типа: картинку, файл, таблицу значений, структуру настроек. Создаётся конструктором Новый ХранилищеЗначений(Значение, Сжатие), а исходное значение достаётся методом Получить().
Частое заблуждение — считать хранилище ещё одной коллекцией вроде массива. Это не так: перебрать его, добавить элемент или найти что-то внутри нельзя. Хранилище работает как запечатанный конверт. Положили значение, записали в базу, потом достали целиком. Всё, что ниже, касается платформы 1С:Предприятие 8.3 и не зависит от конфигурации.
Зачем нужен тип ХранилищеЗначений и как он работает
У реквизитов справочников, документов, регистров и констант ограниченный набор типов: строка, число, дата, булево, ссылки. Записать туда напрямую Структуру, ТаблицуЗначений или ДвоичныеДанные нельзя. Для этого платформа и даёт тип ХранилищеЗначений: его можно выбрать как тип реквизита в конфигураторе.
Механика простая:
- 📦 Вы создаёте объект хранилища и передаёте ему значение.
- 🔒 Платформа сериализует значение во внутренний формат и, если попросили, сжимает.
- 💾 Хранилище присваивается реквизиту и записывается вместе с объектом.
- 📤 При чтении вы вызываете
Получить()и получаете копию исходного значения.
Минимальный пример:
// Положить
Настройки = Новый Структура("Склад, ПоказыватьНулевые", Справочники.Склады.Основной, Ложь);
Объект.НастройкиОтчета = Новый ХранилищеЗначений(Настройки);
// Достать
Настройки = Объект.НастройкиОтчета.Получить();
Если Настройки = Неопределено Тогда
Настройки = Новый Структура("Склад, ПоказыватьНулевые", Неопределено, Ложь);
КонецЕсли;
Проверка на Неопределено обязательна. У нового объекта реквизит пуст, и Получить() вернёт Неопределено.
Сжатие данных
Второй параметр конструктора — объект СжатиеДанных с уровнем от 0 до 9:
Хранилище = Новый ХранилищеЗначений(ДвоичныеДанные, Новый СжатиеДанных(9));
Сжатие заметно уменьшает тексты, XML, JSON и таблицы значений. Для JPEG, PNG, ZIP и PDF выигрыш почти нулевой: эти форматы уже сжаты. Там сжатие только тратит процессор при записи.
При чтении уровень сжатия указывать не нужно. Получить() сама распакует данные.
Хранилище против Массива, Структуры и Соответствия
Путаница возникает потому, что все четыре типа «хранят значения». Но решают они разные задачи.
| Тип | Что это | Можно записать в реквизит базы | Доступ к содержимому |
|---|---|---|---|
Массив | Упорядоченный список по индексу | Нет | По индексу, перебор циклом |
Структура | Набор пар «ключ — значение», ключи-строки | Нет | По имени ключа |
Соответствие | Пары с ключом любого типа | Нет | По ключу, перебор |
ХранилищеЗначений | Упакованное значение для записи в базу | Да | Только целиком через Получить() |
Коллекции живут в памяти, пока работает код. Хранилище нужно, чтобы значение пережило сеанс и оказалось в таблице базы.
Поэтому они не конкурируют, а дополняют друг друга. Типичный приём: собрать настройки в Структуру или Соответствие, упаковать в хранилище, записать. При чтении — распаковать и работать с коллекцией как обычно.
Изменить значение «внутри» хранилища нельзя. Чтобы поправить одну настройку, получите структуру через Получить(), измените ключ и создайте новое хранилище.
Как выгрузить содержимое хранилища в файл и загрузить обратно
Сам объект ХранилищеЗначений в файл не пишется. В файл пишут то, что лежит внутри. Способ зависит от содержимого.
Внутри двоичные данные (файл, картинка)
Это самый частый случай. Достаём ДвоичныеДанные и сохраняем их методом Записать():
&НаСервере
Процедура ВыгрузитьВложение(Ссылка, ПутьКФайлу)
ДД = Ссылка.Вложение.Получить();
Если ТипЗнч(ДД) = Тип("ДвоичныеДанные") Тогда
ДД.Записать(ПутьКФайлу);
КонецЕсли;
КонецПроцедуры
Обратная операция:
ДД = Новый ДвоичныеДанные(ПутьКФайлу);
Объект.Вложение = Новый ХранилищеЗначений(ДД, Новый СжатиеДанных(9));
Путь к файлу в этом коде — на сервере. В клиент-серверном варианте файл окажется на машине с сервером 1С, а не у пользователя. Чтобы отдать файл пользователю, поместите двоичные данные во временное хранилище (ПоместитьВоВременноеХранилище) и на клиенте вызовите НачатьПолучениеФайлаССервера или ПолучитьФайл с полученным адресом.
Внутри структура, таблица, соответствие
Такие значения пишут в файл одним из двух способов:
- 📄
ЗначениеВФайл(Путь, Значение)иЗначениеИзФайла(Путь)— внутренний формат 1С. Быстро, но читается только 1С. - 🧾
СериализаторXDTOиЗаписьXML— XML, который можно открыть и проверить глазами. Сам типХранилищеЗначенийтоже сериализуется в XDTO: в XML он попадает строкой Base64. Поэтому реквизиты-хранилища нормально переносятся в обменах на XML.
Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл(ПутьКФайлу);
СериализаторXDTO.ЗаписатьXML(Запись, Объект.НастройкиОтчета.Получить());
Запись.Закрыть();
Рабочие примеры: картинка, файл, настройки
Как добавить реквизит-хранилище в конфигураторе
Перед любым примером реквизит нужно создать. Порядок в конфигураторе:
- Откройте дерево конфигурации и дважды щёлкните нужный объект — справочник, документ или регистр сведений .
- На вкладке «Данные» добавьте новый реквизит (для регистра — ресурс или реквизит).
- В палитре свойств в поле «Тип» выберите
ХранилищеЗначений. - Сохраните конфигурацию и обновите конфигурацию базы данных (
F7).
Если база работает в клиент-серверном варианте и в ней сидят пользователи, обновление структуры потребует монопольного доступа или динамического обновления. Делайте это вне рабочего времени: при монопольном режиме сеансы пользователей придётся завершить.
Если конфигурация типовая и стоит на поддержке, добавлять реквизит удобнее в расширении. Так обновления типовой не затрут вашу доработку.
Снимок табличной части
Хранилище подходит и для «фотографии» данных на момент события. Например, при согласовании заказа сохраните копию состава, чтобы потом сравнить, что поменяли после утверждения:
Снимок = Объект.Товары.Выгрузить(, "Номенклатура, Количество, Цена");
Объект.СоставПриСогласовании = Новый ХранилищеЗначений(Снимок, Новый СжатиеДанных(9));
Таблица значений хорошо сжимается, поэтому уровень 9 здесь оправдан. Позже Получить() вернёт таблицу, которую можно сравнить с текущей табличной частью построчно.
Фото товара в справочнике
В конфигураторе добавьте справочнику реквизит Фото типа ХранилищеЗначений. Файл пользователь выбирает на клиенте (например, через НачатьПомещениеФайлаНаСервер) и получает адрес во временном хранилище. Дальше — в модуле формы:
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресФото) Тогда
ДД = ПолучитьИзВременногоХранилища(АдресФото);
ТекущийОбъект.Фото = Новый ХранилищеЗначений(ДД);
КонецЕсли;
КонецПроцедуры
АдресФото здесь — реквизит формы типа «Строка». Сжатие для фото не включаем: JPEG уже сжат.
Чтобы показать картинку в поле формы, не тяните двоичные данные на клиент вручную. Получите навигационную ссылку на реквизит:
АдресКартинки = ПолучитьНавигационнуюСсылку(Объект.Ссылка, "Фото");
Строковый реквизит формы с этой ссылкой подключите к полю картинки. Платформа сама загрузит изображение.
Чтение хранилища запросом
Поле типа ХранилищеЗначений можно выбрать в запросе. Распаковка — уже в коде:
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Товары.Ссылка,
| Товары.Фото
|ИЗ
| Справочник.Товары КАК Товары
|ГДЕ
| Товары.Ссылка В (&Список)");
Запрос.УстановитьПараметр("Список", СписокТоваров);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ДД = Выборка.Фото.Получить();
// обработка двоичных данных
КонецЦикла;
Пользовательские настройки обработки
Внешняя обработка часто запоминает параметры между запусками. Удобно держать их в регистре сведений с измерением «Пользователь» и ресурсом-хранилищем. Записываете Структуру с параметрами, при открытии формы — достаёте и раскладываете по полям.
Ограничения хранилища, о которые спотыкаются
| Ограничение | К чему приводит | Что делать |
|---|---|---|
| Нельзя отбирать, сортировать и группировать по полю-хранилищу в запросе | Найти «все товары без фото» условием по содержимому не получится | Заведите рядом булев реквизит-признак или храните размер файла числом |
| Содержимое не видно поиску ссылок | Ссылка внутри хранилища не мешает удалению помеченного объекта | Не храните внутри ссылки, от которых зависит учёт |
| Не всё сериализуется | Ошибка при создании хранилища | Кладите данные, а не «живые» объекты |
| Значение читается целиком | Чтение тяжёлого объекта тянет весь файл | Выносите крупные данные отдельно (см. ниже) |
Подробнее о двух неочевидных пунктах.
🔗 Битые ссылки внутри. Если в хранилище лежит структура со ссылкой на склад, платформа об этом не знает. Склад пометят и удалят — проверка ссылок пройдёт успешно. После этого Получить() вернёт структуру с битой ссылкой («Объект не найден»). Код, который читает настройки, должен это переживать.
🚫 Что нельзя положить. Объекты, привязанные к сеансу и окружению, в хранилище не помещаются: COMОбъект, открытые потоки и записи в файл, формы. Если нужно сохранить «состояние», вынимайте из таких объектов данные — строки, числа, таблицы — и кладите их.
Хранилище не шифрует данные. Пароли и токены в реквизите-хранилище читаются любым кодом с правом чтения объекта. В конфигурациях на БСП для этого есть безопасное хранилище: ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище.
Как не перегрузить базу хранилищами
Хранилище в справочнике удобно, пока файлов мало. Когда их тысячи, а каждый по несколько мегабайт, начинаются проблемы:
- 🐢
Ссылка.ПолучитьОбъект()читает все реквизиты, включая хранилище. Открыли карточку товара ради цены — загрузили фото на 5 МБ. - 📈 Растёт размер базы и выгрузки
.dt, дольше идёт резервное копирование . - 🔄 В распределённой базе и обменах каждое изменение объекта гонит файл заново, даже если меняли только наименование.
Рабочие приёмы оптимизации:
- Выносите тяжёлое из основного объекта. Создайте регистр сведений с измерением-ссылкой на владельца и ресурсом-хранилищем. Справочник остаётся лёгким, файл читается только тогда, когда он нужен.
- В запросах выбирайте только нужные поля. Не пишите
ВЫБРАТЬ *и не обращайтесь через точку к объекту там, где хватит запроса без поля-хранилища. - Сжимайте то, что сжимается. Текст, XML, таблицы —
Новый СжатиеДанных(9). Картинки и архивы — без сжатия. - Уменьшайте картинки до записи. Фото с телефона на 6 МБ для карточки товара не нужно.
- Не храните повторно одно и то же. Если один файл прикрепляют к сотне документов, держите его в одном месте и ссылайтесь.
- Большие объёмы — в тома на диске. Типовые конфигурации на БСП умеют хранить присоединённые файлы в томах файловой системы, а в базе держать только описание.
Чем заменить ХранилищеЗначений в новых конфигурациях
Хранилище никуда не делось и остаётся единственным способом записать в реквизит двоичные данные. Но для части задач в 8.3 есть варианты удобнее.
| Задача | Альтернатива | Когда лучше хранилища |
|---|---|---|
| Передать файл между клиентом и сервером | ПоместитьВоВременноеХранилище / ПолучитьИзВременногоХранилища | Данные нужны только на время сеанса, в базу их писать не надо |
| Сохранить настройки | Строка неограниченной длины с JSON (ЗаписатьJSON) | Нужно читать данные вне 1С или передавать в веб-сервис |
| Настройки форм и отчётов | Стандартные хранилища настроек (ХранилищеОбщихНастроек) | Платформа сама привяжет настройки к пользователю |
| Вложения к документам | Присоединённые файлы БСП | Нужны версии, электронная подпись, хранение в томах |
| Данные, которые фильтруют | Обычные реквизиты и табличные части | Нужны отборы, сортировка, отчёты |
Временное хранилище легко спутать с ХранилищеЗначений из-за похожего названия. Это разные механизмы. Временное хранилище живёт в памяти сеанса и очищается само. ХранилищеЗначений — тип данных, который ложится в таблицу базы навсегда.
JSON в строке выигрывает в читаемости: значение видно прямо в консоли запросов. Но ссылки на объекты базы в JSON придётся сохранять вручную как уникальные идентификаторы. Внутренний формат хранилища делает это сам.
Частые вопросы
Можно ли использовать ХранилищеЗначений на клиенте?
Почему Получить() возвращает Неопределено?
Неопределено и задавайте значение по умолчанию.Как узнать размер данных в хранилище?
ДвоичныеДанные, вызовите Размер() — вернёт число байт. Для быстрых отборов храните размер в отдельном числовом реквизите при записи.Можно ли сделать измерение регистра типа ХранилищеЗначений?
Переносится ли хранилище при выгрузке базы в .dt?
.dt.