ХранилищеЗначения в 1С 8.3: упаковка любых данных в один реквизит

· 9 мин

Содержание

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

Частое заблуждение — считать хранилище значений ещё одной коллекцией вроде таблицы значений, где можно искать строки и сортировать. Это не так. Хранилище не знает, что лежит внутри, и ничего не умеет, кроме «положить» и «отдать». Зачем оно нужно и как работает, лучше всего видно на реквизитах справочников и документов, где хранят вложения и настройки.

Хранилище значений в 1С: суть и основные свойства

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

Основные свойства:

  • 📦 Одно значение внутри. Если нужно сохранить несколько, заверните их в Структура или Соответствие и положите её.
  • 🔒 Содержимое неизменяемо. Метода «заменить значение» нет. Чтобы что-то поменять, создают новое хранилище.
  • 🗜️ Сжатие по выбору. Второй параметр конструктора — объект СжатиеДанных с уровнем от 0 до 9.
  • 🧩 Только сериализуемые значения. Положить можно то, что платформа умеет записать в поток: примитивы, ссылки, коллекции из них, табличный документ, двоичные данные, картинку. Объекты вроде Запрос, COMОбъект, потоков чтения и записи — нельзя.
  • 🗄️ Может быть типом реквизита. В конфигураторе в свойствах реквизита справочника, документа, табличной части или ресурса регистра сведений выбирают тип ХранилищеЗначения.
  • 🖥️ Живёт на сервере. С самим объектом работают в серверных процедурах. На тонкий клиент передают уже извлечённые данные — например, через временное хранилище.

Не путайте хранилище значений с хранилищем конфигурации (групповая разработка) и с хранилищами настроек (ХранилищеОбщихНастроек, ХранилищеСистемныхНастроек). Это три разных механизма с похожими названиями.

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

Создание — одна строка. Первый параметр — что храним, второй — сжатие. Если второй параметр не указан, данные сохраняются без сжатия.

// Без сжатия
Хранилище = Новый ХранилищеЗначения("Любая строка");

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

Пустое хранилище тоже существует: у нового элемента справочника реквизит типа ХранилищеЗначения уже заполнен, и его Получить() вернёт Неопределено.

Какой уровень сжатия выбирать

Что хранимУровень СжатиеДанныхПочему
Таблица значений, структура, текст, XML9Такие данные хорошо сжимаются, выигрыш заметный
Табличный документ (печатная форма, отчёт)9Много повторяющейся разметки
JPEG, PNG, PDF, ZIP0 или без параметраФайл уже сжат, процессор тратится впустую
Мелкие значения (флаг, дата, одна ссылка)без параметраХранилище тут вообще под вопросом, см. ниже

Хранение нескольких значений в одном хранилище

Удобный приём — собрать всё в структуру:

Данные = Новый Структура;
Данные.Вставить("Версия", 2);
Данные.Вставить("Колонки", МассивКолонок);
Данные.Вставить("Отбор", ТаблицаОтбора);

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

Поле Версия внутри — не прихоть. Через год формат настройки поменяется, и код чтения сможет отличить старые записи от новых.

Запись и изменение данных в хранилище значений

Рабочий сценарий — положить файл в реквизит объекта и потом его достать.

Запись файла в реквизит

&НаСервере
Процедура ЗаписатьКартинкуВТовар(Ссылка, ИмяФайла)

	ДД = Новый ДвоичныеДанные(ИмяФайла);

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

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

Здесь Картинка — реквизит справочника с типом ХранилищеЗначения. Сжатие не указано: картинка в JPEG уже сжата.

Чтение обратно

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

Проверка на Неопределено обязательна: у части элементов картинки может не быть.

Изменение содержимого

Хранилище неизменяемо, поэтому порядок всегда один:

  1. Достать значение: Таблица = Объект.Настройки.Получить();
  2. Изменить его как обычную коллекцию: добавить строки, поправить колонки.
  3. Создать новое хранилище и присвоить реквизиту: Объект.Настройки = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));
  4. Записать объект.

Шаг 3 часто забывают. Таблица меняется в памяти, объект записывается, а в базе остаётся старое содержимое.

Сохранение печатной формы или отчёта

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

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

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

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

На тонкий клиент сам объект ХранилищеЗначения не передают. Стандартный приём — временное хранилище и адрес в реквизите формы (тип «Строка»):

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

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

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

&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)

	Если ЭтоАдресВременногоХранилища(АдресКартинки) Тогда
		ТекущийОбъект.Картинка = Новый ХранилищеЗначения(
			ПолучитьИзВременногоХранилища(АдресКартинки));
	КонецЕсли;

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

Поле картинки на форме привязывают к реквизиту АдресКартинки. Когда пользователь выбирает новый файл, клиентский код помещает его во временное хранилище тем же УникальныйИдентификатор, и при записи он попадёт в объект.

Поиск, сортировка и фильтрация данных из хранилища значений

Здесь главное ограничение типа. СУБД видит реквизит ХранилищеЗначения как непрозрачный набор байтов.

Что это значит на практике:

  • 🚫 Поле нельзя использовать в условиях ГДЕ, в УПОРЯДОЧИТЬ ПО, в СГРУППИРОВАТЬ ПО и в соединениях.
  • 🚫 По нему нельзя построить индекс и сделать отбор в динамическом списке.
  • ✅ Поле можно выбрать в запросе, а затем вызвать Получить() у значения в выборке.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Товары.Ссылка КАК Ссылка,
|	Товары.Настройки КАК Настройки
|ИЗ
|	Справочник.Товары КАК Товары
|ГДЕ
|	Товары.ПометкаУдаления = ЛОЖЬ";

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
	Таблица = Выборка.Настройки.Получить();
	Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
		Продолжить;
	КонецЕсли;
	Найденные = Таблица.НайтиСтроки(Новый Структура("Активна", Истина));
	// обработка найденных строк
КонецЦикла;

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

Такой код читает и распаковывает хранилище у каждой записи. На 50 000 элементов с таблицами внутри это минуты и гигабайты памяти. Если по значению нужно искать регулярно — выносите его в отдельный реквизит или в регистр сведений.

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

Как сравнить два хранилища

Оператор = для двух объектов ХранилищеЗначения не сравнивает содержимое. Сравнивают извлечённые данные:

  • для двоичных данных — хеш-сумму через ХешированиеДанных;
  • для коллекций и структур — строковое представление через ЗначениеВСтрокуВнутр().
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.Добавить(Хранилище1.Получить());
Хеш1 = Хеш.ХешСумма;

Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.Добавить(Хранилище2.Получить());
Хеш2 = Хеш.ХешСумма;

ОдинаковыеФайлы = (Хеш1 = Хеш2);

Этот пример — для хранилищ с двоичными данными внутри. Хеш удобно сохранить в отдельный строковый реквизит при записи файла: тогда поиск дубликатов файлов делается обычным запросом.

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

Выбор зависит от того, сколько живут данные и нужен ли по ним поиск.

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

В типовых конфигурациях на БСП для файлов есть подсистема «Работа с файлами», и она умеет хранить их в томах на диске. Если вы дорабатываете типовую, лучше использовать её, а не заводить свой реквизит-хранилище: база не раздувается, а файлы видны в общем списке вложений.

Что вы хотите сохранить?

Частые ошибки с хранилищем значений и как их избежать

Ссылки внутри хранилища не видит поиск ссылок. Если в хранилище лежит таблица со ссылками на контрагентов, при удалении помеченных объектов платформа не узнает, что они где-то используются. Элемент удалится, в таблице останутся «битые» ссылки. Ключевые ссылки дублируйте в обычной табличной части, если их удаление критично.

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

Получить() вызывают в цикле много раз. Каждый вызов — заново десериализация. Достаньте значение один раз в переменную и работайте с ней.

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

Сжимают уже сжатое. СжатиеДанных(9) для JPEG или ZIP не даёт выигрыша, но нагружает сервер при каждой записи.

Кладут в хранилище то, что потом нужно в отчётах. Через полгода приходит задача «покажите все товары, у которых в настройках стоит флаг», и выясняется, что запросом её не решить. Решайте заранее, какие поля могут понадобиться для отбора.

Частые вопросы о хранилище значений

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