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

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

Как сохранить значение в хранилище значений 1С

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

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

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

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

Что такое хранилище значений и когда его применять

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

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

Не стоит использовать хранилище там, где достаточно обычных реквизитов. Данные внутри хранилища нельзя индексировать, искать по ним запросом и выводить в отчёты напрямую — сначала их нужно извлечь. Если по значению планируется отбор или соединение в запросе, храните его в отдельном реквизите обычного типа.

Подготовка: создание реквизита с типом «ХранилищеЗначения»

Первый шаг — добавить в объект конфигурации реквизит нужного типа. Откройте конфигуратор, найдите нужный справочник, документ или регистр сведений, добавьте реквизит (или измерение/ресурс для регистра) и в палитре свойств установите тип «ХранилищеЗначения». Для регистра сведений хранилище обычно делают ресурсом, а ключевые поля — измерениями.

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

Как записать значение в хранилище: код и порядок действий

Запись выполняется в три шага: получить или создать объект, присвоить реквизиту новый объект хранилища, записать объект. Ниже типовой пример для справочника, где сохраняется таблица значений:

ТаблицаДанных = Новый ТаблицаЗначений;

ТаблицаДанных.Колонки.Добавить("Товар");

ТаблицаДанных.Колонки.Добавить("Цена");

// ... заполнение таблицы ...

Элемент = Справочники.НастройкиОбмена.СоздатьЭлемент();

Элемент.Наименование = "Настройки выгрузки";

Элемент.ДанныеНастроек = Новый ХранилищеЗначения(ТаблицаДанных);

Элемент.Записать();

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

Порядок записи значения
  1. 1
    Создайте или получите объект (элемент справочника, документ, запись регистра)
  2. 2
    Подготовьте данные и убедитесь, что они сериализуются (нет ссылок на объекты БД внутри структур)
  3. 3
    Присвойте реквизиту значение Новый ХранилищеЗначения(Данные)
  4. 4
    Вызовите метод Записать() и обработайте возможные исключения
  5. 5
    Проверьте результат чтением в отдельной процедуре

Для регистра сведений запись выглядит иначе — через набор записей или менеджер записи:

МенеджерЗаписи = РегистрыСведений.ПараметрыОбмена.СоздатьМенеджерЗаписи();

МенеджерЗаписи.Период = ТекущаяДатаСеанса();

МенеджерЗаписи.Узел = УзелСсылка;

МенеджерЗаписи.Настройки = Новый ХранилищеЗначения(СтруктураНастроек);

МенеджерЗаписи.Записать();

Чтение значения из хранилища и проверка результата

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

Запрос = Новый Запрос(

"ВЫБРАТЬ ДанныеНастроек ИЗ Справочник.НастройкиОбмена

|ГДЕ Ссылка = &Ссылка");

Запрос.УстановитьПараметр("Ссылка", ТекущаяНастройка);

Выборка = Запрос.Выполнить().Выбрать();

Если Выборка.Следующий() Тогда

Данные = Выборка.ДанныеНастроек.Получить();

Если Данные = Неопределено Тогда

ВызватьИсключение "Настройки не заполнены";

КонецЕсли;

КонецЕсли;

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

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

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

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

Метод Получить() возвращает данные, идентичные записанным

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

Возвращается Неопределено или данные усечены

Повторное открытие объекта в новом сеансе
Норма

Значение доступно после перезапуска сеанса

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

Значение теряется — объект не был записан

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

Рост соответствует ожидаемому объёму данных

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

База раздувается кратно — данные дублируются или не сжимаются

Что можно и нельзя помещать в хранилище

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

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

Способы упаковки данных

Новый ХранилищеЗначения(Значение)

Штатный способ для реквизитов типа ХранилищеЗначения. Подходит для коллекций без ссылок на объекты БД

ЗначениеВСтрокуВнутр + строковый реквизит

Сохраняет ссылки корректно, но требует реквизита неограниченной строки и не сжимает данные

JSON в строковом реквизите

Удобен для обмена с внешними системами, но ссылки и специфические типы 1С придётся преобразовывать вручную

Сжатие данных и контроль объёма базы

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

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

Типичные ошибки
01
Присвоение реквизиту самого значения вместо Новый ХранилищеЗначения — ошибка несоответствия типов при записи
02
Чтение без проверки на Неопределено — падение кода на пустых записях
03
Сохранение ссылок на объекты внутри структуры — потеря связей при переносе данных между базами
04
Хранение больших файлов в реквизите основного объекта — раздувание таблицы и замедление запросов
05
Многократная перезапись хранилища в цикле без необходимости — лишняя нагрузка на СУБД

Типичные ошибки и как их диагностировать

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

Ошибка «Несоответствие типов» при записи означает, что реквизиту присвоено значение исходного типа вместо хранилища. Ошибка сериализации при вызове конструктора указывает на несериализуемый объект внутри данных — найдите его, пройдя по структуре в отладчике и проверив тип каждого элемента.

Маршрут решения

Как выбрать подход

Нужно сохранить таблицу значений, структуру или картинку без ссылокИспользуйте реквизит типа ХранилищеЗначения и конструктор Новый ХранилищеЗначения
В данных есть ссылки на объекты базыСериализуйте через ЗначениеВСтрокуВнутр в строковый реквизит неограниченной длины
Сохраняются файлы больше нескольких мегабайтВынесите данные в отдельный регистр сведений, в основном объекте храните только ссылку
По значению нужен поиск или отбор в запросахНе используйте хранилище — добавьте обычный индексируемый реквизит

FAQ

Можно ли хранить в хранилище значений картинку или файл?

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

Почему метод Получить() возвращает Неопределено, хотя данные записывались?

Чаще всего объект не был записан после присвоения реквизита, либо запись перекрыта другим сеансом. Также проверьте, что читаете именно тот реквизит и ту же запись: в регистрах сведений несовпадение одного измерения даёт пустую выборку.

Можно ли найти записи по содержимому хранилища в запросе?

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

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

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

Есть ли ограничение на размер значения в хранилище?

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

Как очистить хранилище, чтобы освободить место?

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

Итог

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

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