Разработка и Конфигурация · 11 мин
Хранилище значения в 1С: что это и как с ним работать
Что такое тип ХранилищеЗначения в 1С, зачем он нужен, как сохранять и извлекать данные, работать с картинками и файлами, типичные ошибки и примеры кода.
Тип «Хранилище значения» (ХранилищеЗначения) в 1С — это универсальный контейнер, который позволяет сохранить в реквизите объекта данные любого типа: картинки, файлы, таблицы значений, структуры, сериализованные объекты. Без него невозможно, например, хранить фотографии товаров в справочнике «Номенклатура» или прикреплённые документы в договорах.
Главная особенность типа в том, что платформа сериализует содержимое и сохраняет его в базу в бинарном виде. В запросах и отчётах такое поле нельзя сравнивать, группировать или выводить напрямую — сначала данные нужно извлечь методом Получить(). Это одновременно и ограничение, и источник большинства ошибок начинающих разработчиков.
Ниже разобрано, как устроен тип, как правильно помещать в него данные и извлекать их обратно, какие подводные камни встречаются при работе с файлами и картинками, а также когда хранилище значения применять не стоит.
Что такое хранилище значения и как оно устроено
ХранилищеЗначения — это объект встроенного языка, который оборачивает произвольное значение и позволяет записать его в базу данных. При записи платформа сериализует содержимое (преобразует в поток байт), при необходимости сжимает его и сохраняет в поле типа BLOB или IMAGE в СУБД.
Создать хранилище можно конструктором с одним или двумя параметрами: само значение и необязательный алгоритм сжатия (СжатиеДанных). Второй параметр имеет смысл указывать, если хранятся объёмные текстовые данные или несжатые изображения — для уже сжатых форматов (JPEG, ZIP) эффекта не будет, а процессорное время на упаковку будет тратиться зря.
Важно понимать: хранилище значения — это не ссылка на данные, а сами данные. Каждое чтение реквизита с хранилищем из базы поднимает в память весь объём. Если в справочнике тысяча элементов с фотографиями по несколько мегабайт, выборка всех элементов целиком станет тяжёлой операцией.
Как поместить данные в хранилище и прочитать их обратно
Базовый сценарий состоит из трёх действий: создать объект хранилища с нужным значением, присвоить его реквизиту объекта, записать объект. Извлечение выполняется зеркально — вызовом метода Получить() у значения реквизита.
// Запись
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Товар");
ТЗ.Колонки.Добавить("Цена");
// ... заполнение таблицы ...
ОбъектСправочника = Справочники.НастройкиОбмена.СоздатьЭлемент();
ОбъектСправочника.Наименование = "Настройки выгрузки";
ОбъектСправочника.ДанныеНастройки = Новый ХранилищеЗначения(ТЗ, Новый СжатиеДанных(9));
ОбъектСправочника.Записать();
// Чтение
Выборка = Справочники.НастройкиОбмена.Выбрать();
Пока Выборка.Следующий() Цикл
СохранённыеДанные = Выборка.ДанныеНастройки.Получить();
Если ТипЗнч(СохранённыеДанные) = Тип("ТаблицаЗначений") Тогда
// работаем с таблицей
КонецЕсли;
КонецЦикла;
Обязательно проверяйте тип результата после Получить(). Если реквизит не заполнен, метод вернёт Неопределено, и обращение к свойствам результата без проверки вызовет ошибку выполнения.
ДвоичныеДанные = Новый ДвоичныеДанные("C:\Файлы\отчет.pdf");
Объект.ФайлВложение = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
Объект.Записать();Работа с картинками и файлами
Самая частая прикладная задача — хранение изображений, например фотографий товаров. Картинку можно поместить в хранилище двумя способами: как объект Картинка или как ДвоичныеДанные. Второй вариант предпочтительнее: он не зависит от того, сможет ли платформа распознать формат изображения, и сохраняет файл байт-в-байт.
// Помещение файла картинки
ДвоичныеДанные = Новый ДвоичныеДанные(ИмяФайлаНаДиске);
ТоварОбъект.Фото = Новый ХранилищеЗначения(ДвоичныеДанные);
ТоварОбъект.Записать();
// Вывод картинки в форму
ДанныеКартинки = ТоварОбъект.Фото.Получить();
Если ДанныеКартинки <> Неопределено Тогда
АдресВХранилище = ПоместитьВоВременноеХранилище(ДанныеКартинки, УникальныйИдентификатор);
// АдресВХранилище назначается элементу формы типа "Поле картинки"
КонецЕсли;
Для отображения картинки в управляемой форме недостаточно просто прочитать реквизит — двоичные данные нужно поместить во временное хранилище формы и связать адрес с полем картинки. Это стандартный приём, который используется и в типовых конфигурациях.
Ограничения типа в запросах и отчётах
Поле типа ХранилищеЗначения нельзя использовать в условиях ГДЕ, соединениях, группировках и упорядочивании запроса. Платформа просто не умеет сравнивать сериализованные данные. В запросе такое поле можно только выбрать, после чего работать с ним в коде через Получить().
Из этого следует важное архитектурное правило: всё, по чему нужно искать, фильтровать или строить отчёты, должно храниться в обычных типизированных реквизитах. Хранилище значения — только для «мёртвого груза»: настроек, вложений, снимков данных, которые читаются целиком и не участвуют в аналитике.
В СКД и отчётах содержимое хранилища напрямую не выводится. Если нужно показать, например, миниатюру фото товара в списке, придётся программно формировать отчёт или использовать дополнительный реквизит с адресом временного хранилища.
Когда использовать хранилище значения, а когда нет
Хранилище значения оправдано в ограниченном наборе сценариев: сохранение настроек отчётов и обработок, хранение небольших изображений и печатей, снимки табличных данных для истории, кэширование результатов тяжёлых вычислений, передача сложных структур через параметры сеанса или фоновые задания.
Есть ситуации, где хранилище — плохой выбор. Нормализованные данные, по которым нужен поиск, следует хранить в обычных реквизитах и табличных частях. Большие файлы лучше размещать в томах на диске. Простые строки и числа не нужно оборачивать в хранилище — это только усложнит чтение и отладку.
Сравнение вариантов
Подходит для настроек, небольших картинок, снимков данных. Ограничение: нельзя искать по содержимому, рост размера базы.
Подходит для документов, сканов, больших вложений. Даёт версии, подписи, тома хранения. Ограничение: зависимость от БСП.
Подходят для любых данных, участвующих в поиске, отборах и отчётах. Ограничение: фиксированный состав типов.
Типичные ошибки и как их избежать
Одна из самых распространённых ошибок — попытка сравнить реквизит-хранилище с чем-либо в запросе или условии. Запрос либо не выполнится, либо даст неожиданный результат. Проверку «заполнено ли хранилище» в запросе выполняют косвенно — например, через отдельный булев реквизит-признак, который выставляется при записи.
Вторая частая проблема — потеря данных при обмене. Хранилище значения корректно переносится планами обмена только если сериализованный объект существует в обеих конфигурациях. Сериализованная таблица значений с колонками составных типов может не восстановиться на другой стороне, если состав типов отличается. Для обмена надёжнее сериализовать данные в строку XML или JSON средствами ЗаписьXML/ЗаписьJSON и хранить уже строку.
Третья ошибка — чтение хранилища в цикле по большой выборке. Каждый вызов Получить() десериализует данные заново. Если нужно обработать много объектов, читайте хранилище только там, где оно действительно требуется, и не выбирайте это поле «на всякий случай».
Проверка результата и отладка
После записи объекта убедитесь, что данные действительно сохранились: прочитайте объект заново из базы и вызовите Получить(). Проверять нужно не только факт непустого результата, но и тип восстановленного значения — он должен совпадать с тем, что помещали.
При отладке полезно выводить размер двоичных данных: у объекта ДвоичныеДанные есть метод Размер(). Если после сохранения файла размер внезапно равен нулю, значит, файл не был прочитан (неверный путь, нет прав доступа) либо хранилище было перезаписано пустым значением в другом месте кода.
Как проверить результат
Возвращается объект ожидаемого типа (ТаблицаЗначений, ДвоичныеДанные и т.д.)
Неопределено или значение другого типа
Соответствует размеру исходного файла
Ноль или существенно меньше ожидаемого
Изображение отображается после помещения во временное хранилище
Пустое поле картинки или ошибка формата
Что важно запомнить
ХранилищеЗначения — контейнер для данных любого типа, которые не участвуют в поиске и отборах. Запись — через Новый ХранилищеЗначения, чтение — через Получить() с обязательной проверкой на Неопределено. Для больших файлов и вложений используйте специализированные механизмы, а критерии поиска держите в обычных реквизитах.
Частые вопросы
Можно ли найти все объекты, где хранилище значения заполнено?
Напрямую в запросе — нет, содержимое хранилища не сравнивается. Практические варианты: завести отдельный булев реквизит-признак «ЕстьВложение» и поддерживать его при записи, либо перебрать объекты в коде с проверкой Получить() <> Неопределено, что допустимо только на небольших объёмах данных.
Чем хранилище значения отличается от временного хранилища?
ХранилищеЗначения — прикладной тип для постоянного сохранения данных в реквизитах базы. Временное хранилище — серверный механизм передачи данных между вызовами в рамках сеанса (например, для показа картинки в форме); его содержимое живёт ограниченное время и не пишется в базу само по себе.
Как сохранить в хранилище структуру с настройками?
Структура сериализуется напрямую: Новый ХранилищеЗначения(МояСтруктура). При чтении через Получить() вы получите структуру обратно. Учитывайте, что если в структуре лежат объекты, которых нет в другой конфигурации при обмене, восстановление может завершиться ошибкой — для обмена надёжнее JSON.
Влияет ли сжатие данных на скорость работы?
Да, в обе стороны. Сжатие уменьшает объём в базе и ускоряет чтение по сети, но тратит процессорное время на упаковку и распаковку. Для текстовых данных и таблиц сжатие обычно выгодно, для уже сжатых форматов (JPEG, ZIP, видео) — бесполезно.
Почему после обмена данными хранилище не читается?
Наиболее вероятная причина — несовпадение состава типов в конфигурациях: сериализованный объект ссылается на тип, которого нет в базе-приёмнике. Решение — хранить передаваемые данные в универсальном формате (XML, JSON) в виде строки и парсить их на стороне получателя.
Можно ли хранить в одном реквизите и картинки, и файлы?
Технически да, так как тип не фиксирует содержимое. Но практичнее хранить двоичные данные любых файлов, а тип содержимого определять по отдельному реквизиту с расширением или именем файла — тогда один механизм обслуживает все форматы.