Реквизит типа ХранилищеЗначения: как хранить картинки, файлы и коллекции в базе 1С

· 9 мин

Содержание

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

Вариантов хранить «нестандартные» данные в 1С несколько. Строка неограниченной длины подходит для текста и JSON, отдельные реквизиты — для данных, по которым нужен отбор. Реквизит типа хранилище значений выбирают, когда нужно положить в базу двоичное содержимое или целую коллекцию и доставать её только целиком, без поиска внутри.

Зачем нужен тип ХранилищеЗначения и что в нём хранят

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

Что обычно кладут в хранилище:

  • 🖼️ фотографию товара, логотип организации, скан подписи;
  • 📎 содержимое файла — PDF, XLSX, XML, полученные от контрагента;
  • 📊 настройки: Структура, Соответствие, ТаблицаЗначений с параметрами обработки;
  • 🧾 готовый ТабличныйДокумент, чтобы показать печатную форму без повторного формирования;
  • 🗂️ снимок данных «как было» перед изменением — для истории или отката.

Что положить нельзя: значения, которые не сериализуются. Это формы, COM-объекты, потоки чтения и записи, объекты соединений. Платформа на таком значении выдаст ошибку ещё в конструкторе.

Хранилище — «чёрный ящик» для СУБД. База видит только набор байтов. Отбор, поиск и сортировка по содержимому невозможны: чтобы что-то узнать, значение нужно извлечь целиком.

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

Реквизит настраивают так же, как любой другой, меняется только тип.

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

Длины, точности и квалификаторов у типа нет. Не получится и сделать составной тип «ХранилищеЗначения + что-то ещё».

Где реквизит такого типа допустим

Объект метаданныхМожно лиКомментарий
Справочник, документ, план видов характеристикДаРеквизит и реквизит табличной части
КонстантаДаУдобно для логотипа или общих настроек
Регистр сведенийДаКак ресурс или реквизит, не как измерение
Измерение регистраНетИзмерение участвует в индексе и отборах
Реквизит формыДаНо только для работы на сервере

Свойства, которые станут недоступны

У реквизита типа ХранилищеЗначения нельзя включить индексирование. В динамическом списке по нему не настроить отбор и сортировку, а в отчётах на СКД его не сгруппировать.

Через расширение

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

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

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

Запись значения

&НаСервере
Процедура СохранитьНастройки(СсылкаНаЭлемент, Настройки)

    // Настройки — например, Структура с параметрами
    Объект = СсылкаНаЭлемент.ПолучитьОбъект();
    Объект.Настройки = Новый ХранилищеЗначения(Настройки);
    Объект.Записать();

КонецПроцедуры

Чтение значения

Настройки = СсылкаНаЭлемент.Настройки.Получить();
Если Настройки = Неопределено Тогда
    // Хранилище пустое: реквизит ещё ни разу не заполняли
    Настройки = Новый Структура("Период, Склад", Неопределено, Неопределено);
КонецЕсли;

У пустого реквизита Получить() возвращает Неопределено. Проверка обязательна: без неё код упадёт при первом же обращении к полю структуры.

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

Второй параметр конструктора — объект СжатиеДанных с уровнем от 0 до 9. Ноль — без сжатия, 9 — максимальное.

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

Сжатие заметно уменьшает XML, текст, таблицы значений и BMP. JPEG, PNG, ZIP и PDF со сжатыми потоками уже упакованы, выигрыш на них — единицы процентов, а время записи растёт. Для таких файлов сжатие можно не включать.

Сериализация для обмена

Хранилище значений само по себе переносится в планах обмена и при выгрузке в XML: сериализатор XDTO пишет его как двоичные данные в Base64. Если нужно передать содержимое в стороннюю систему, извлеките значение через Получить() и выгружайте уже его. Внутренний формат хранилища другой системе непонятен.

Для отладки пригодится пара ЗначениеВСтрокуВнутр() / ЗначениеИзСтрокиВнутр(): она даёт строковое представление сериализуемого значения. Хранить так данные вместо хранилища смысла нет: строка занимает больше места и не сжимается.

Хранилище значений в управляемой форме: картинки и файлы

Главный подводный камень — доступность типа. ХранилищеЗначения работает на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте его нет, поэтому весь код с хранилищем выполняется в процедурах &НаСервере.

Отсюда второе правило. Реквизит объекта типа ХранилищеЗначения не попадает в данные формы Объект. Написать на клиенте Объект.Картинка = ... нельзя, а изменение, сделанное в Объект на сервере, при записи не сохранится. Значение передают через событие записи.

Загрузка файла в реквизит

Схема такая:

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

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

КонецПроцедуры

Показ картинки на форме

Поле картинки на форме привязывают к строковому реквизиту с адресом. Для уже записанного элемента подойдёт навигационная ссылка на реквизит:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

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

КонецПроцедуры

Для нового, ещё не записанного объекта используйте временное хранилище: ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор). Второй параметр привязывает данные к форме, и платформа очистит их сама при её закрытии.

Выгрузка файла пользователю

Чтобы отдать файл на клиент, на сервере извлеките ДвоичныеДанные через Получить(), поместите во временное хранилище и передайте адрес на клиент. Там файл получают методом сохранения (НачатьПолучениеФайлаССервера или аналогичным, в зависимости от режима совместимости).

Ограничения хранилища значений и влияние на производительность

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

ОграничениеЧто из него следует
Нельзя использовать в условиях, группировке и сортировке запросаОтобрать «товары без фото» запросом по содержимому не получится — заведите отдельный флаг
Значение читается целикомЧтобы узнать одно поле структуры, придётся распаковать всю структуру
Хранится в той же таблице, что и объектКаждое чтение объекта тянет за собой мегабайты из хранилища
Размер ограничен СУБДВ PostgreSQL двоичное поле — до 1 ГБ; на практике проблемы начинаются гораздо раньше
Нет на тонком клиентеВся работа — через сервер и временное хранилище

Почему тормозят списки и проведение

Ссылка.Картинка или ПолучитьОбъект() читают из базы всю строку объекта вместе с хранилищем. Если в справочнике «Номенклатура» 30 000 позиций с фото по 500 КБ, перебор через объекты прогонит через сервер около 15 ГБ.

Запросы здесь безопаснее: поле хранилища выбирается, только если оно указано в тексте запроса. Но конструкция ВЫБРАТЬ * и объектное чтение в цикле — частые причины медленных обработок.

Тяжёлые данные — в отдельный объект

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

Добавлять хранилище с крупными файлами в часто проводимый документ — плохая идея. Каждое перепроведение перезаписывает строку документа, а с ней и двоичный блок. Растут и база, и журнал транзакций.

Хранилище значений и другие способы хранения данных

СпособКогда выбиратьМинусы
ХранилищеЗначения в реквизите объектаНебольшие данные (до сотен КБ), нужные почти всегда вместе с объектомУтяжеляет объект, нет поиска
Отдельный регистр сведений с хранилищемФайлы, фото, история версийНужен дополнительный код чтения и записи
Строка неограниченной длиныТекст, JSON, XML, которые хочется видеть «как есть»Нет сжатия, нельзя использовать в условиях запроса без ПОДСТРОКА
Отдельные реквизиты или табличная частьДанные, по которым нужен отбор, отчёт, сортировкаКаждое поле — изменение структуры
Файлы на диске (тома)Гигабайты вложенийРезервная копия базы не включает файлы, их копируют отдельно
Временное хранилищеПередача данных между клиентом и сервером в пределах сеансаДанные живут до конца сеанса или закрытия формы
ХранилищеОбщихНастроекПользовательские настройки форм и отчётовЭто механизм настроек, а не реквизит объекта

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

Примеры: как использовать хранилище значений на практике

Сохранить таблицу значений и восстановить её

// Запись
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Склад");
Таблица.Колонки.Добавить("Лимит", Новый ОписаниеТипов("Число"));
// ... заполнение
Константы.ЛимитыСкладов.Установить(Новый ХранилищеЗначения(Таблица));

// Чтение
Таблица = Константы.ЛимитыСкладов.Получить().Получить();
Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
    Таблица = Новый ТаблицаЗначений;
КонецЕсли;

Двойное Получить() здесь не опечатка. Первое читает значение константы (это хранилище), второе распаковывает из него таблицу.

Сохранить файл с диска сервера в справочник

&НаСервере
Процедура ЗагрузитьСкан(СсылкаНаДоговор, ПутьКФайлу)

    Данные = Новый ДвоичныеДанные(ПутьКФайлу);
    Объект = СсылкаНаДоговор.ПолучитьОбъект();
    Объект.Скан = Новый ХранилищеЗначения(Данные);
    Объект.Записать();

КонецПроцедуры

Путь указывается на компьютере, где работает сервер 1С, а не у пользователя. Для файлов пользователя нужна схема с временным хранилищем из раздела про формы.

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

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

Снимок табличной части перед изменением

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

Ошибки при работе с хранилищем значений

Диагностика: выберите симптом

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

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