Реквизит типа ХранилищеЗначения: как хранить картинки, файлы и коллекции в базе 1С
Содержание
Хранилище значений в 1С — это тип данных ХранилищеЗначения, который позволяет сохранить в реквизите справочника, документа, константы или регистра сведений почти любое сериализуемое значение: картинку, файл (ДвоичныеДанные), таблицу значений, структуру, табличный документ. Записывают его конструктором Новый ХранилищеЗначения(Значение), читают методом Получить(). Тип общий для платформы 8.3 и не зависит от конфигурации.
Вариантов хранить «нестандартные» данные в 1С несколько. Строка неограниченной длины подходит для текста и JSON, отдельные реквизиты — для данных, по которым нужен отбор. Реквизит типа хранилище значений выбирают, когда нужно положить в базу двоичное содержимое или целую коллекцию и доставать её только целиком, без поиска внутри.
Зачем нужен тип ХранилищеЗначения и что в нём хранят
Обычный реквизит в 1С хранит примитивное значение или ссылку. Таблицу значений, массив или картинку туда не положишь: у СУБД нет для них колонки. ХранилищеЗначения решает задачу так: платформа сериализует значение во внутренний формат и пишет его в базу как двоичный блок.
Что обычно кладут в хранилище:
- 🖼️ фотографию товара, логотип организации, скан подписи;
- 📎 содержимое файла — PDF, XLSX, XML, полученные от контрагента;
- 📊 настройки:
Структура,Соответствие,ТаблицаЗначенийс параметрами обработки; - 🧾 готовый
ТабличныйДокумент, чтобы показать печатную форму без повторного формирования; - 🗂️ снимок данных «как было» перед изменением — для истории или отката.
Что положить нельзя: значения, которые не сериализуются. Это формы, COM-объекты, потоки чтения и записи, объекты соединений. Платформа на таком значении выдаст ошибку ещё в конструкторе.
Хранилище — «чёрный ящик» для СУБД. База видит только набор байтов. Отбор, поиск и сортировка по содержимому невозможны: чтобы что-то узнать, значение нужно извлечь целиком.
Как создать реквизит хранилища значений в Конфигураторе
Реквизит настраивают так же, как любой другой, меняется только тип.
- Откройте Конфигуратор и дерево конфигурации (если база на поддержке, сначала включите возможность изменения или используйте расширение).
- Найдите нужный объект: справочник, документ, регистр сведений или константу.
- Добавьте реквизит в ветку
Реквизиты(или в табличную часть). - В палитре свойств нажмите на поле
Типи выберитеХранилищеЗначения. - Сохраните конфигурацию и выполните обновление конфигурации
базы данных (
F7).
Длины, точности и квалификаторов у типа нет. Не получится и сделать составной тип «ХранилищеЗначения + что-то ещё».
Где реквизит такого типа допустим
| Объект метаданных | Можно ли | Комментарий |
|---|---|---|
| Справочник, документ, план видов характеристик | Да | Реквизит и реквизит табличной части |
| Константа | Да | Удобно для логотипа или общих настроек |
| Регистр сведений | Да | Как ресурс или реквизит, не как измерение |
| Измерение регистра | Нет | Измерение участвует в индексе и отборах |
| Реквизит формы | Да | Но только для работы на сервере |
Свойства, которые станут недоступны
У реквизита типа ХранилищеЗначения нельзя включить индексирование. В динамическом списке по нему не настроить отбор
и сортировку, а в отчётах на СКД его не сгруппировать.
Через расширение
Если менять основную конфигурацию нельзя, реквизит добавляют в расширение конфигурации
(они появились в платформе 8.3.6). Заимствуйте объект, добавьте реквизит с типом ХранилищеЗначения, затем обновите конфигурацию базы данных.
Запись и чтение хранилища значений в коде
Работа с хранилищем значений в коде сводится к двум операциям: упаковать значение и распаковать его.
Запись значения
&НаСервере
Процедура СохранитьНастройки(СсылкаНаЭлемент, Настройки)
// Настройки — например, Структура с параметрами
Объект = СсылкаНаЭлемент.ПолучитьОбъект();
Объект.Настройки = Новый ХранилищеЗначения(Настройки);
Объект.Записать();
КонецПроцедуры
Чтение значения
Настройки = СсылкаНаЭлемент.Настройки.Получить();
Если Настройки = Неопределено Тогда
// Хранилище пустое: реквизит ещё ни разу не заполняли
Настройки = Новый Структура("Период, Склад", Неопределено, Неопределено);
КонецЕсли;
У пустого реквизита Получить() возвращает Неопределено. Проверка обязательна: без неё код упадёт при первом же обращении к полю структуры.
Сжатие данных
Второй параметр конструктора — объект СжатиеДанных с уровнем от 0 до 9. Ноль — без сжатия, 9 — максимальное.
Объект.Файл = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
Сжатие заметно уменьшает XML, текст, таблицы значений и BMP. JPEG, PNG, ZIP и PDF со сжатыми потоками уже упакованы, выигрыш на них — единицы процентов, а время записи растёт. Для таких файлов сжатие можно не включать.
Сериализация для обмена
Хранилище значений само по себе переносится в планах обмена и при выгрузке в XML: сериализатор XDTO пишет его как двоичные данные в Base64. Если нужно передать содержимое в стороннюю систему, извлеките значение через Получить() и выгружайте уже его. Внутренний формат хранилища другой системе непонятен.
Для отладки пригодится пара ЗначениеВСтрокуВнутр() / ЗначениеИзСтрокиВнутр(): она даёт строковое представление сериализуемого значения. Хранить так данные вместо хранилища смысла нет: строка занимает больше места и не сжимается.
Хранилище значений в управляемой форме: картинки и файлы
Главный подводный камень — доступность типа. ХранилищеЗначения работает на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте его нет, поэтому весь код с хранилищем выполняется в процедурах &НаСервере.
Отсюда второе правило. Реквизит объекта типа ХранилищеЗначения не попадает в данные формы Объект. Написать на клиенте Объект.Картинка = ... нельзя, а изменение, сделанное в Объект на сервере, при записи не сохранится. Значение передают через событие записи.
Загрузка файла в реквизит
Схема такая:
- На клиенте пользователь выбирает файл. Методы помещения файла (
НачатьПомещениеФайлаи его асинхронные аналоги) возвращают адрес во временном хранилище. - Адрес сохраняют в реквизит формы типа «Строка», например
АдресНовогоФайла. - В обработчике
ПередЗаписьюНаСервереберут данные по адресу и кладут вТекущийОбъект.
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ЗначениеЗаполнено(АдресНовогоФайла) Тогда
Данные = ПолучитьИзВременногоХранилища(АдресНовогоФайла);
ТекущийОбъект.Картинка = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));
УдалитьИзВременногоХранилища(АдресНовогоФайла);
АдресНовогоФайла = "";
КонецЕсли;
КонецПроцедуры
Показ картинки на форме
Поле картинки на форме привязывают к строковому реквизиту с адресом. Для уже записанного элемента подойдёт навигационная ссылка на реквизит:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
АдресКартинки = ПолучитьНавигационнуюСсылку(Объект.Ссылка, "Картинка");
КонецЕсли;
КонецПроцедуры
Для нового, ещё не записанного объекта используйте временное хранилище: ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор). Второй параметр привязывает данные к форме, и платформа очистит их сама при её закрытии.
Выгрузка файла пользователю
Чтобы отдать файл на клиент, на сервере извлеките ДвоичныеДанные через Получить(), поместите во временное хранилище и передайте адрес на клиент. Там файл получают методом сохранения (НачатьПолучениеФайлаССервера или аналогичным, в зависимости от режима совместимости).
Ограничения хранилища значений и влияние на производительность
Хранилище удобно, но за удобство платят скоростью. Вот что важно знать заранее.
| Ограничение | Что из него следует |
|---|---|
| Нельзя использовать в условиях, группировке и сортировке запроса | Отобрать «товары без фото» запросом по содержимому не получится — заведите отдельный флаг |
| Значение читается целиком | Чтобы узнать одно поле структуры, придётся распаковать всю структуру |
| Хранится в той же таблице, что и объект | Каждое чтение объекта тянет за собой мегабайты из хранилища |
| Размер ограничен СУБД | В PostgreSQL двоичное поле — до 1 ГБ; на практике проблемы начинаются гораздо раньше |
| Нет на тонком клиенте | Вся работа — через сервер и временное хранилище |
Почему тормозят списки и проведение
Ссылка.Картинка или ПолучитьОбъект() читают из базы всю строку объекта вместе с хранилищем. Если в справочнике «Номенклатура» 30 000 позиций с фото по 500 КБ, перебор через объекты прогонит через сервер около 15 ГБ.
Запросы здесь безопаснее: поле хранилища выбирается, только если оно указано в тексте запроса. Но конструкция ВЫБРАТЬ * и объектное чтение в цикле — частые причины медленных обработок.
Тяжёлые данные — в отдельный объект
Типовой приём: вынести содержимое в отдельный регистр сведений или справочник с двумя полями — ссылка на владельца и само хранилище. Основной объект остаётся лёгким, а файл читают только тогда, когда он нужен. Так устроены присоединённые файлы в конфигурациях на БСП: содержимое лежит отдельно от карточки, а большие объёмы выносят из базы в тома на диске.
Добавлять хранилище с крупными файлами в часто проводимый документ — плохая идея. Каждое перепроведение перезаписывает строку документа, а с ней и двоичный блок. Растут и база, и журнал транзакций.
Хранилище значений и другие способы хранения данных
| Способ | Когда выбирать | Минусы |
|---|---|---|
ХранилищеЗначения в реквизите объекта | Небольшие данные (до сотен КБ), нужные почти всегда вместе с объектом | Утяжеляет объект, нет поиска |
| Отдельный регистр сведений с хранилищем | Файлы, фото, история версий | Нужен дополнительный код чтения и записи |
| Строка неограниченной длины | Текст, JSON, XML, которые хочется видеть «как есть» | Нет сжатия, нельзя использовать в условиях запроса без ПОДСТРОКА |
| Отдельные реквизиты или табличная часть | Данные, по которым нужен отбор, отчёт, сортировка | Каждое поле — изменение структуры |
| Файлы на диске (тома) | Гигабайты вложений | Резервная копия базы не включает файлы, их копируют отдельно |
| Временное хранилище | Передача данных между клиентом и сервером в пределах сеанса | Данные живут до конца сеанса или закрытия формы |
ХранилищеОбщихНастроек | Пользовательские настройки форм и отчётов | Это механизм настроек, а не реквизит объекта |
Тип ДвоичныеДанные в реквизит объекта напрямую не поставить: для записи двоичных данных в базу их всегда оборачивают в хранилище.
Примеры: как использовать хранилище значений на практике
Сохранить таблицу значений и восстановить её
// Запись
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Склад");
Таблица.Колонки.Добавить("Лимит", Новый ОписаниеТипов("Число"));
// ... заполнение
Константы.ЛимитыСкладов.Установить(Новый ХранилищеЗначения(Таблица));
// Чтение
Таблица = Константы.ЛимитыСкладов.Получить().Получить();
Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
Таблица = Новый ТаблицаЗначений;
КонецЕсли;
Двойное Получить() здесь не опечатка. Первое читает значение константы (это хранилище), второе распаковывает из него таблицу.
Сохранить файл с диска сервера в справочник
&НаСервере
Процедура ЗагрузитьСкан(СсылкаНаДоговор, ПутьКФайлу)
Данные = Новый ДвоичныеДанные(ПутьКФайлу);
Объект = СсылкаНаДоговор.ПолучитьОбъект();
Объект.Скан = Новый ХранилищеЗначения(Данные);
Объект.Записать();
КонецПроцедуры
Путь указывается на компьютере, где работает сервер 1С, а не у пользователя. Для файлов пользователя нужна схема с временным хранилищем из раздела про формы.
Выгрузить содержимое обратно в файл
Данные = СсылкаНаДоговор.Скан.Получить();
Если ТипЗнч(Данные) = Тип("ДвоичныеДанные") Тогда
Данные.Записать(КаталогВременныхФайлов() + "скан.pdf");
КонецЕсли;
Снимок табличной части перед изменением
Перед массовой правкой табличной части её выгружают методом Выгрузить() в таблицу значений и записывают в хранилище в регистр сведений «История изменений» с датой и пользователем. Если правка оказалась ошибочной, таблицу распаковывают и загружают обратно методом Загрузить().
Ошибки при работе с хранилищем значений
Объект, а хранилище туда не попадает. Перенесите присвоение в ПередЗаписьюНаСервере и пишите в ТекущийОбъект.Получить() вернул Неопределено: реквизит ещё не заполняли. Проверяйте результат через ТипЗнч() перед использованием.&НаСервере или в серверный общий модуль, на клиент передавайте адрес временного хранилища.ВЫБРАТЬ * с тяжёлым хранилищем. Выбирайте в запросе только нужные поля, а крупные данные вынесите в отдельный регистр.Частые вопросы
Можно ли положить хранилище значений внутрь другого хранилища?
Как узнать размер данных в хранилище?
ДвоичныеДанные, вызовите у него метод Размер() — он вернёт число байт. Для коллекций точного размера нет, ориентируйтесь на размер ЗначениеВСтрокуВнутр().Попадает ли хранилище в выгрузку .dt и резервную копию?
Как найти все элементы, где хранилище пустое?
Получить() = Неопределено один раз и заполните служебный признак, дальше отбирайте по нему.