К статье
Практический материал

Разработка и Конфигурация · 11 мин

Хранилище значений как реквизит в 1С: практическое руководство

Как использовать тип ХранилищеЗначения в реквизитах 1С: создание, запись и чтение данных, сериализация объектов, типичные ошибки и ограничения.

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

Типичные сценарии применения: хранение фотографий товаров, сканов документов, настроек отчётов, произвольных наборов данных, которые неудобно раскладывать по табличным частям. Разработчик получает один реквизит вместо проектирования отдельной структуры таблиц.

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

Что такое ХранилищеЗначения и как оно устроено

ХранилищеЗначения — это объект платформы, который сериализует переданное значение в двоичный формат и опционально сжимает его алгоритмом Deflate. В базе данных реквизит этого типа физически хранится как BLOB-поле (в терминах MS SQL — image/varbinary(max), в PostgreSQL — bytea).

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

Второй параметр конструктора — степень сжатия (объект «СтепеньСжатия»). Для текстовых данных и таблиц значений сжатие обычно уменьшает объём в несколько раз. Для уже сжатых форматов (JPEG, ZIP) эффекта почти нет — только лишняя нагрузка на процессор.

Как добавить реквизит типа ХранилищеЗначения

В конфигураторе откройте объект (справочник, документ, план счетов) и добавьте новый реквизит. В свойстве «Тип» выберите «ХранилищеЗначения». Этот тип нельзя комбинировать с другими в составном типе — реквизит будет строго однотипным.

Обратите внимание на ограничения: для реквизита с типом ХранилищеЗначения недоступны признаки «Индексировать», «Полнотекстовый поиск», использование в критериях отбора ограничено. Платформа сознательно блокирует эти возможности, потому что содержимое BLOB-поля непрозрачно для СУБД.

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

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

Запись и чтение данных программно

Запись выполняется в два шага: создаётся объект хранилища с нужным содержимым, затем он присваивается реквизиту. Чтение — через метод «Получить()», который возвращает исходный объект или Неопределено, если хранилище пустое.

// Запись таблицы значений в реквизит

Объект.НастройкиОтбора = Новый ХранилищеЗначения(ТаблицаНастроек, Новый СжатиеДанных(9));

Объект.Записать();

// Чтение

СохраненныеДанные = Объект.НастройкиОтбора.Получить();

Если СохраненныеДанные <> Неопределено Тогда

// работаем с таблицей значений

КонецЕсли;

bsl
// Сохранение картинки из файла в реквизит справочника
ДвоичныеДанные = Новый ДвоичныеДанные("C:\Images\photo.jpg");
ТоварОбъект = СсылкаНаТовар.ПолучитьОбъект();
ТоварОбъект.Фотография = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
ТоварОбъект.Записать();

// Чтение обратно
Данные = ТоварОбъект.Фотография.Получить();
Если ТипЗнч(Данные) = Тип("ДвоичныеДанные") Тогда
    АдресКартинки = ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификаторФормы);
КонецЕсли;

Важная деталь: метод «Получить()» каждый раз десериализует объект заново. Если вызывать его в цикле по тысяче элементов справочника, получите тысячу полных распаковок BLOB-данных — это заметная нагрузка. Кэшируйте результат, если объект нужен многократно.

Проверка на пустоту — частая точка ошибок. Сравнение «Реквизит = Неопределено» не работает: реквизит всегда содержит объект типа ХранилищеЗначения. Правильно вызывать «Получить()» и сравнивать результат с Неопределено.

Хранилище значений в запросах и СКД

В языке запросов реквизит типа ХранилищеЗначения можно выбрать как поле, но нельзя использовать в условиях ГДЕ, соединениях и группировках по содержимому. Результат запроса вернёт объект ХранилищеЗначения, из которого уже в коде извлекают данные методом «Получить()».

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

Когда хранилище значений — правильный выбор

Классические оправданные сценарии: файлы и изображения (если не используется подсистема работы с файлами БСП), снимки настроек пользователей, результаты обменов в исходном виде, произвольные структуры данных с переменным составом полей, которые не участвуют в аналитике.

Обратная сторона: размер базы. Файлы в BLOB-полях раздувают таблицы, замедляют полные бэкапы и репликацию. При массовом хранении файлов рассмотрите тома на диске (механизм «Хранение файлов в томах» в БСП) — база хранит только ссылки, а сами файлы лежат в файловой системе.

Способы хранения неструктурированных данных

Реквизит ХранилищеЗначения

Простая реализация, данные внутри транзакции объекта. Ограничение: нет поиска по содержимому, рост размера таблиц.

Табличная часть или регистр сведений

Данные доступны в запросах и отчётах. Подходит, когда структура данных фиксирована и нужна аналитика.

Файловые тома (БСП)

Файлы хранятся на диске, в базе — только ссылки. Оптимально для больших объёмов, но требует настройки и резервного копирования томов отдельно.

Типичные ошибки и подводные камни

Первая ошибка — хранение ссылок на удаляемые объекты. Хранилище сериализует ссылку, но не защищает её от удаления: после удаления объекта при десериализации получите «битую» ссылку. Платформа не контролирует ссылочную целостность внутри BLOB.

Вторая — изменение структуры сохранённых данных. Если вы сохранили таблицу значений с пятью колонками, а потом код ожидает семь, старые записи вернут таблицу старого формата. Всегда проверяйте состав данных после «Получить()» или версионируйте формат, добавляя в сохраняемую структуру поле «Версия».

Типичные ошибки
01
Сравнение реквизита с Неопределено вместо вызова Получить() — условие никогда не сработает, так как реквизит всегда содержит объект хранилища
02
Вызов Получить() в цикле по большой выборке — многократная десериализация резко замедляет обработку
03
Хранение больших файлов без сжатия и без оценки роста базы — таблицы раздуваются, бэкапы тормозят
04
Сохранение ссылок без контроля удаления — после удаления объекта в хранилище остаются битые ссылки
05
Попытка использовать содержимое в отборе динамического списка — платформа это запрещает, нужна другая структура данных

Третья группа проблем — обмены. При выгрузке в XML хранилище значений сериализуется в base64, что увеличивает объём примерно на треть. В планах обмена большие хранилища заметно раздувают сообщения. Для РИБ это может стать узким местом канала.

Проверка результата и контроль качества

После внедрения реквизита проверьте полный цикл: запись объекта, закрытие и повторное открытие формы, чтение данных. Убедитесь, что при копировании элемента справочника хранилище копируется корректно (платформа копирует значение реквизита, но стоит проверить на вашем коде обработчиков).

Контрольные точки

Как проверить результат

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

Получить() возвращает объект исходного типа с полными данными

Тревожный признак

Возвращается Неопределено или объект другого типа — проверяйте код записи

Размер базы после массовой загрузки
Норма

Прирост соответствует суммарному объёму данных с учётом сжатия

Тревожный признак

Таблица выросла в разы больше ожидаемого — сжатие не применяется

Поведение при обмене
Норма

Сообщения обмена приемлемого размера, данные восстанавливаются на приёмнике

Тревожный признак

Сообщения раздулись, обмены тормозят — выносите файлы из хранилища

Для отладки содержимого удобна небольшая обработка, которая выводит тип и краткое описание результата «Получить()» для выбранного элемента. Это быстрее, чем каждый раз писать код в консоли, и снижает риск случайной порчи данных при экспериментах на рабочей базе.

Итог

Что важно запомнить

ХранилищеЗначения — реквизит для произвольных объектов, не участвующих в поиске и отборах. Запись через конструктор, чтение через Получить() с проверкой на Неопределено. Следите за размером базы, версионируйте формат данных и не храните ссылки на удаляемые объекты. Для файлов рассмотрите типовую подсистему БСП.

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

Можно ли искать элементы по содержимому хранилища значений?

Нет, напрямую нельзя. Содержимое BLOB-поля непрозрачно для СУБД и языка запросов. Если поиск нужен, дублируйте ключевые данные в отдельном строковом реквизите или регистре сведений и ищите по нему, а хранилище используйте только для полного пакета данных.

Что вернёт Получить(), если в реквизит ничего не записывали?

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

Можно ли хранить в одном реквизите разные типы данных?

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

Сжимаются ли данные автоматически?

Нет. Сжатие включается только при явной передаче второго параметра конструктора — объекта СтепеньСжатия. Без него данные сериализуются без сжатия. Для текстов и таблиц сжатие почти всегда выгодно, для JPEG и архивов — бесполезно.

Сохранятся ли данные хранилища при обмене РИБ?

Да, реквизиты типа ХранилищеЗначения передаются в сообщениях обмена и корректно восстанавливаются на узле-приёмнике. Учитывайте только рост размера сообщений из-за кодирования двоичных данных — при больших объёмах это замедляет обмен.

Как очистить устаревшие данные из хранилищ?

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