Тип ХранилищеЗначений: как положить в реквизит базы файл, картинку или структуру

· 9 мин

Содержание

ХранилищеЗначений в 1С — это обёртка, которая позволяет записать в реквизит базы данных значение почти любого типа: картинку, файл, таблицу значений, структуру настроек. Создаётся конструктором Новый ХранилищеЗначений(Значение, Сжатие), а исходное значение достаётся методом Получить().

Частое заблуждение — считать хранилище ещё одной коллекцией вроде массива. Это не так: перебрать его, добавить элемент или найти что-то внутри нельзя. Хранилище работает как запечатанный конверт. Положили значение, записали в базу, потом достали целиком. Всё, что ниже, касается платформы 1С:Предприятие 8.3 и не зависит от конфигурации.

Зачем нужен тип ХранилищеЗначений и как он работает

У реквизитов справочников, документов, регистров и констант ограниченный набор типов: строка, число, дата, булево, ссылки. Записать туда напрямую Структуру, ТаблицуЗначений или ДвоичныеДанные нельзя. Для этого платформа и даёт тип ХранилищеЗначений: его можно выбрать как тип реквизита в конфигураторе.

Механика простая:

  1. 📦 Вы создаёте объект хранилища и передаёте ему значение.
  2. 🔒 Платформа сериализует значение во внутренний формат и, если попросили, сжимает.
  3. 💾 Хранилище присваивается реквизиту и записывается вместе с объектом.
  4. 📤 При чтении вы вызываете Получить() и получаете копию исходного значения.

Минимальный пример:

// Положить
Настройки = Новый Структура("Склад, ПоказыватьНулевые", Справочники.Склады.Основной, Ложь);
Объект.НастройкиОтчета = Новый ХранилищеЗначений(Настройки);

// Достать
Настройки = Объект.НастройкиОтчета.Получить();
Если Настройки = Неопределено Тогда
    Настройки = Новый Структура("Склад, ПоказыватьНулевые", Неопределено, Ложь);
КонецЕсли;

Проверка на Неопределено обязательна. У нового объекта реквизит пуст, и Получить() вернёт Неопределено.

Сжатие данных

Второй параметр конструктора — объект СжатиеДанных с уровнем от 0 до 9:

Хранилище = Новый ХранилищеЗначений(ДвоичныеДанные, Новый СжатиеДанных(9));

Сжатие заметно уменьшает тексты, XML, JSON и таблицы значений. Для JPEG, PNG, ZIP и PDF выигрыш почти нулевой: эти форматы уже сжаты. Там сжатие только тратит процессор при записи.

При чтении уровень сжатия указывать не нужно. Получить() сама распакует данные.

Хранилище против Массива, Структуры и Соответствия

Путаница возникает потому, что все четыре типа «хранят значения». Но решают они разные задачи.

ТипЧто этоМожно записать в реквизит базыДоступ к содержимому
МассивУпорядоченный список по индексуНетПо индексу, перебор циклом
СтруктураНабор пар «ключ — значение», ключи-строкиНетПо имени ключа
СоответствиеПары с ключом любого типаНетПо ключу, перебор
ХранилищеЗначенийУпакованное значение для записи в базуДаТолько целиком через Получить()

Коллекции живут в памяти, пока работает код. Хранилище нужно, чтобы значение пережило сеанс и оказалось в таблице базы.

Поэтому они не конкурируют, а дополняют друг друга. Типичный приём: собрать настройки в Структуру или Соответствие, упаковать в хранилище, записать. При чтении — распаковать и работать с коллекцией как обычно.

Изменить значение «внутри» хранилища нельзя. Чтобы поправить одну настройку, получите структуру через Получить(), измените ключ и создайте новое хранилище.

Как выгрузить содержимое хранилища в файл и загрузить обратно

Сам объект ХранилищеЗначений в файл не пишется. В файл пишут то, что лежит внутри. Способ зависит от содержимого.

Внутри двоичные данные (файл, картинка)

Это самый частый случай. Достаём ДвоичныеДанные и сохраняем их методом Записать():

&НаСервере
Процедура ВыгрузитьВложение(Ссылка, ПутьКФайлу)
    ДД = Ссылка.Вложение.Получить();
    Если ТипЗнч(ДД) = Тип("ДвоичныеДанные") Тогда
        ДД.Записать(ПутьКФайлу);
    КонецЕсли;
КонецПроцедуры

Обратная операция:

ДД = Новый ДвоичныеДанные(ПутьКФайлу);
Объект.Вложение = Новый ХранилищеЗначений(ДД, Новый СжатиеДанных(9));

Путь к файлу в этом коде — на сервере. В клиент-серверном варианте файл окажется на машине с сервером 1С, а не у пользователя. Чтобы отдать файл пользователю, поместите двоичные данные во временное хранилище (ПоместитьВоВременноеХранилище) и на клиенте вызовите НачатьПолучениеФайлаССервера или ПолучитьФайл с полученным адресом.

Внутри структура, таблица, соответствие

Такие значения пишут в файл одним из двух способов:

  • 📄 ЗначениеВФайл(Путь, Значение) и ЗначениеИзФайла(Путь) — внутренний формат 1С. Быстро, но читается только 1С.
  • 🧾 СериализаторXDTO и ЗаписьXML — XML, который можно открыть и проверить глазами. Сам тип ХранилищеЗначений тоже сериализуется в XDTO: в XML он попадает строкой Base64. Поэтому реквизиты-хранилища нормально переносятся в обменах на XML.
Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл(ПутьКФайлу);
СериализаторXDTO.ЗаписатьXML(Запись, Объект.НастройкиОтчета.Получить());
Запись.Закрыть();

Рабочие примеры: картинка, файл, настройки

Как добавить реквизит-хранилище в конфигураторе

Перед любым примером реквизит нужно создать. Порядок в конфигураторе:

  1. Откройте дерево конфигурации и дважды щёлкните нужный объект — справочник, документ или регистр сведений .
  2. На вкладке «Данные» добавьте новый реквизит (для регистра — ресурс или реквизит).
  3. В палитре свойств в поле «Тип» выберите ХранилищеЗначений.
  4. Сохраните конфигурацию и обновите конфигурацию базы данных (F7).

Если база работает в клиент-серверном варианте и в ней сидят пользователи, обновление структуры потребует монопольного доступа или динамического обновления. Делайте это вне рабочего времени: при монопольном режиме сеансы пользователей придётся завершить.

Если конфигурация типовая и стоит на поддержке, добавлять реквизит удобнее в расширении. Так обновления типовой не затрут вашу доработку.

Снимок табличной части

Хранилище подходит и для «фотографии» данных на момент события. Например, при согласовании заказа сохраните копию состава, чтобы потом сравнить, что поменяли после утверждения:

Снимок = Объект.Товары.Выгрузить(, "Номенклатура, Количество, Цена");
Объект.СоставПриСогласовании = Новый ХранилищеЗначений(Снимок, Новый СжатиеДанных(9));

Таблица значений хорошо сжимается, поэтому уровень 9 здесь оправдан. Позже Получить() вернёт таблицу, которую можно сравнить с текущей табличной частью построчно.

Фото товара в справочнике

В конфигураторе добавьте справочнику реквизит Фото типа ХранилищеЗначений. Файл пользователь выбирает на клиенте (например, через НачатьПомещениеФайлаНаСервер) и получает адрес во временном хранилище. Дальше — в модуле формы:

&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    Если ЭтоАдресВременногоХранилища(АдресФото) Тогда
        ДД = ПолучитьИзВременногоХранилища(АдресФото);
        ТекущийОбъект.Фото = Новый ХранилищеЗначений(ДД);
    КонецЕсли;
КонецПроцедуры

АдресФото здесь — реквизит формы типа «Строка». Сжатие для фото не включаем: JPEG уже сжат.

Чтобы показать картинку в поле формы, не тяните двоичные данные на клиент вручную. Получите навигационную ссылку на реквизит:

АдресКартинки = ПолучитьНавигационнуюСсылку(Объект.Ссылка, "Фото");

Строковый реквизит формы с этой ссылкой подключите к полю картинки. Платформа сама загрузит изображение.

Чтение хранилища запросом

Поле типа ХранилищеЗначений можно выбрать в запросе. Распаковка — уже в коде:

Запрос = Новый Запрос(
"ВЫБРАТЬ
|    Товары.Ссылка,
|    Товары.Фото
|ИЗ
|    Справочник.Товары КАК Товары
|ГДЕ
|    Товары.Ссылка В (&Список)");
Запрос.УстановитьПараметр("Список", СписокТоваров);

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    ДД = Выборка.Фото.Получить();
    // обработка двоичных данных
КонецЦикла;

Пользовательские настройки обработки

Внешняя обработка часто запоминает параметры между запусками. Удобно держать их в регистре сведений с измерением «Пользователь» и ресурсом-хранилищем. Записываете Структуру с параметрами, при открытии формы — достаёте и раскладываете по полям.

Ограничения хранилища, о которые спотыкаются

ОграничениеК чему приводитЧто делать
Нельзя отбирать, сортировать и группировать по полю-хранилищу в запросеНайти «все товары без фото» условием по содержимому не получитсяЗаведите рядом булев реквизит-признак или храните размер файла числом
Содержимое не видно поиску ссылокСсылка внутри хранилища не мешает удалению помеченного объектаНе храните внутри ссылки, от которых зависит учёт
Не всё сериализуетсяОшибка при создании хранилищаКладите данные, а не «живые» объекты
Значение читается целикомЧтение тяжёлого объекта тянет весь файлВыносите крупные данные отдельно (см. ниже)

Подробнее о двух неочевидных пунктах.

🔗 Битые ссылки внутри. Если в хранилище лежит структура со ссылкой на склад, платформа об этом не знает. Склад пометят и удалят — проверка ссылок пройдёт успешно. После этого Получить() вернёт структуру с битой ссылкой («Объект не найден»). Код, который читает настройки, должен это переживать.

🚫 Что нельзя положить. Объекты, привязанные к сеансу и окружению, в хранилище не помещаются: COMОбъект, открытые потоки и записи в файл, формы. Если нужно сохранить «состояние», вынимайте из таких объектов данные — строки, числа, таблицы — и кладите их.

Хранилище не шифрует данные. Пароли и токены в реквизите-хранилище читаются любым кодом с правом чтения объекта. В конфигурациях на БСП для этого есть безопасное хранилище: ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище.

Как не перегрузить базу хранилищами

Хранилище в справочнике удобно, пока файлов мало. Когда их тысячи, а каждый по несколько мегабайт, начинаются проблемы:

  • 🐢 Ссылка.ПолучитьОбъект() читает все реквизиты, включая хранилище. Открыли карточку товара ради цены — загрузили фото на 5 МБ.
  • 📈 Растёт размер базы и выгрузки .dt, дольше идёт резервное копирование .
  • 🔄 В распределённой базе и обменах каждое изменение объекта гонит файл заново, даже если меняли только наименование.

Рабочие приёмы оптимизации:

  1. Выносите тяжёлое из основного объекта. Создайте регистр сведений с измерением-ссылкой на владельца и ресурсом-хранилищем. Справочник остаётся лёгким, файл читается только тогда, когда он нужен.
  2. В запросах выбирайте только нужные поля. Не пишите ВЫБРАТЬ * и не обращайтесь через точку к объекту там, где хватит запроса без поля-хранилища.
  3. Сжимайте то, что сжимается. Текст, XML, таблицы — Новый СжатиеДанных(9). Картинки и архивы — без сжатия.
  4. Уменьшайте картинки до записи. Фото с телефона на 6 МБ для карточки товара не нужно.
  5. Не храните повторно одно и то же. Если один файл прикрепляют к сотне документов, держите его в одном месте и ссылайтесь.
  6. Большие объёмы — в тома на диске. Типовые конфигурации на БСП умеют хранить присоединённые файлы в томах файловой системы, а в базе держать только описание.
Где хранить данные в вашем случае?

Чем заменить ХранилищеЗначений в новых конфигурациях

Хранилище никуда не делось и остаётся единственным способом записать в реквизит двоичные данные. Но для части задач в 8.3 есть варианты удобнее.

ЗадачаАльтернативаКогда лучше хранилища
Передать файл между клиентом и серверомПоместитьВоВременноеХранилище / ПолучитьИзВременногоХранилищаДанные нужны только на время сеанса, в базу их писать не надо
Сохранить настройкиСтрока неограниченной длины с JSON (ЗаписатьJSON)Нужно читать данные вне 1С или передавать в веб-сервис
Настройки форм и отчётовСтандартные хранилища настроек (ХранилищеОбщихНастроек)Платформа сама привяжет настройки к пользователю
Вложения к документамПрисоединённые файлы БСПНужны версии, электронная подпись, хранение в томах
Данные, которые фильтруютОбычные реквизиты и табличные частиНужны отборы, сортировка, отчёты

Временное хранилище легко спутать с ХранилищеЗначений из-за похожего названия. Это разные механизмы. Временное хранилище живёт в памяти сеанса и очищается само. ХранилищеЗначений — тип данных, который ложится в таблицу базы навсегда.

JSON в строке выигрывает в читаемости: значение видно прямо в консоли запросов. Но ссылки на объекты базы в JSON придётся сохранять вручную как уникальные идентификаторы. Внутренний формат хранилища делает это сам.

Частые вопросы

Можно ли использовать ХранилищеЗначений на клиенте?
Создавать и записывать хранилище в реквизиты правильнее на сервере. С клиента передавайте двоичные данные через временное хранилище, а упаковку делайте в серверной процедуре.
Почему Получить() возвращает Неопределено?
Реквизит ещё не заполнялся, либо объект прочитан до записи хранилища. Проверяйте результат на Неопределено и задавайте значение по умолчанию.
Как узнать размер данных в хранилище?
Получите значение и, если это ДвоичныеДанные, вызовите Размер() — вернёт число байт. Для быстрых отборов храните размер в отдельном числовом реквизите при записи.
Можно ли сделать измерение регистра типа ХранилищеЗначений?
Нет, по хранилищу нельзя индексировать и отбирать. Хранилище делают ресурсом или реквизитом регистра, а измерением — ссылку на владельца данных.
Переносится ли хранилище при выгрузке базы в .dt?
Да, содержимое реквизитов-хранилищ входит в выгрузку информационной базы целиком. Поэтому крупные файлы в хранилищах прямо увеличивают размер .dt.