ХранилищеЗначения в 1С 8.3: упаковка любых данных в один реквизит
Содержание
Хранилище значений в 1С — это тип ХранилищеЗначения: контейнер, который упаковывает одно значение (картинку, файл, таблицу значений, структуру) в сериализованном и при желании сжатом виде. Его создают конструктором Новый ХранилищеЗначения(Значение, Сжатие), а достают содержимое единственным методом Получить().
Частое заблуждение — считать хранилище значений ещё одной коллекцией вроде таблицы значений, где можно искать строки и сортировать. Это не так. Хранилище не знает, что лежит внутри, и ничего не умеет, кроме «положить» и «отдать». Зачем оно нужно и как работает, лучше всего видно на реквизитах справочников и документов, где хранят вложения и настройки.
Хранилище значений в 1С: суть и основные свойства
Тип ХранилищеЗначения решает одну задачу: сохранить в базе значение, у которого нет своего «табличного» представления. Строку или число можно положить в обычный реквизит. А вот таблицу значений, табличный документ или двоичные данные файла — только через хранилище.
Основные свойства:
- 📦 Одно значение внутри. Если нужно сохранить несколько, заверните их в
СтруктураилиСоответствиеи положите её. - 🔒 Содержимое неизменяемо. Метода «заменить значение» нет. Чтобы что-то поменять, создают новое хранилище.
- 🗜️ Сжатие по выбору. Второй параметр конструктора — объект
СжатиеДанныхс уровнем от 0 до 9. - 🧩 Только сериализуемые значения. Положить можно то, что платформа умеет записать в поток: примитивы, ссылки, коллекции из них, табличный документ, двоичные данные, картинку. Объекты вроде
Запрос,COMОбъект, потоков чтения и записи — нельзя. - 🗄️ Может быть типом реквизита. В конфигураторе в свойствах реквизита справочника, документа, табличной части или ресурса регистра сведений выбирают тип
ХранилищеЗначения. - 🖥️ Живёт на сервере. С самим объектом работают в серверных процедурах. На тонкий клиент передают уже извлечённые данные — например, через временное хранилище.
Не путайте хранилище значений с хранилищем конфигурации (групповая разработка) и с хранилищами настроек (ХранилищеОбщихНастроек, ХранилищеСистемныхНастроек). Это три разных механизма с похожими названиями.
Как создать и инициализировать хранилище значений
Создание — одна строка. Первый параметр — что храним, второй — сжатие. Если второй параметр не указан, данные сохраняются без сжатия.
// Без сжатия
Хранилище = Новый ХранилищеЗначения("Любая строка");
// С максимальным сжатием
Хранилище = Новый ХранилищеЗначения(ТаблицаНастроек, Новый СжатиеДанных(9));
Пустое хранилище тоже существует: у нового элемента справочника реквизит типа ХранилищеЗначения уже заполнен, и его Получить() вернёт Неопределено.
Какой уровень сжатия выбирать
| Что храним | Уровень СжатиеДанных | Почему |
|---|---|---|
| Таблица значений, структура, текст, XML | 9 | Такие данные хорошо сжимаются, выигрыш заметный |
| Табличный документ (печатная форма, отчёт) | 9 | Много повторяющейся разметки |
| JPEG, PNG, PDF, ZIP | 0 или без параметра | Файл уже сжат, процессор тратится впустую |
| Мелкие значения (флаг, дата, одна ссылка) | без параметра | Хранилище тут вообще под вопросом, см. ниже |
Хранение нескольких значений в одном хранилище
Удобный приём — собрать всё в структуру:
Данные = Новый Структура;
Данные.Вставить("Версия", 2);
Данные.Вставить("Колонки", МассивКолонок);
Данные.Вставить("Отбор", ТаблицаОтбора);
Объект.Настройки = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));
Поле Версия внутри — не прихоть. Через год формат настройки поменяется, и код чтения сможет отличить старые записи от новых.
Запись и изменение данных в хранилище значений
Рабочий сценарий — положить файл в реквизит объекта и потом его достать.
Запись файла в реквизит
&НаСервере
Процедура ЗаписатьКартинкуВТовар(Ссылка, ИмяФайла)
ДД = Новый ДвоичныеДанные(ИмяФайла);
Товар = Ссылка.ПолучитьОбъект();
Товар.Картинка = Новый ХранилищеЗначения(ДД);
Товар.Записать();
КонецПроцедуры
Здесь Картинка — реквизит справочника с типом ХранилищеЗначения. Сжатие не указано: картинка в JPEG уже сжата.
Чтение обратно
ДД = Ссылка.Картинка.Получить();
Если ДД <> Неопределено Тогда
ДД.Записать(КаталогВременныхФайлов() + "picture.jpg");
КонецЕсли;
Проверка на Неопределено обязательна: у части элементов картинки может не быть.
Изменение содержимого
Хранилище неизменяемо, поэтому порядок всегда один:
- Достать значение:
Таблица = Объект.Настройки.Получить(); - Изменить его как обычную коллекцию: добавить строки, поправить колонки.
- Создать новое хранилище и присвоить реквизиту:
Объект.Настройки = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9)); - Записать объект.
Шаг 3 часто забывают. Таблица меняется в памяти, объект записывается, а в базе остаётся старое содержимое.
Сохранение печатной формы или отчёта
Табличный документ тоже сериализуется. Это удобно, когда нужно зафиксировать печатную форму в момент проведения и потом показывать именно её, а не пересобранную по новым данным:
ТабДок = СформироватьПечатнуюФорму(ДокументСсылка);
Запись = РегистрыСведений.АрхивПечатныхФорм.СоздатьМенеджерЗаписи();
Запись.Документ = ДокументСсылка;
Запись.Форма = Новый ХранилищеЗначения(ТабДок, Новый СжатиеДанных(9));
Запись.Записать();
Здесь АрхивПечатныхФорм — ваш регистр сведений
с измерением Документ и ресурсом Форма типа ХранилищеЗначения, а СформироватьПечатнуюФорму — ваша функция, возвращающая ТабличныйДокумент. Для показа достаточно получить значение и вызвать у табличного документа Показать() на клиенте, передав его туда из серверной функции.
Хранилище значений на управляемой форме
На тонкий клиент сам объект ХранилищеЗначения не передают. Стандартный приём — временное хранилище и адрес в реквизите формы (тип «Строка»):
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
АдресКартинки = ПоместитьВоВременноеХранилище(
Объект.Ссылка.Картинка.Получить(), УникальныйИдентификатор);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресКартинки) Тогда
ТекущийОбъект.Картинка = Новый ХранилищеЗначения(
ПолучитьИзВременногоХранилища(АдресКартинки));
КонецЕсли;
КонецПроцедуры
Поле картинки на форме привязывают к реквизиту АдресКартинки. Когда пользователь выбирает новый файл, клиентский код помещает его во временное хранилище тем же УникальныйИдентификатор, и при записи он попадёт в объект.
Поиск, сортировка и фильтрация данных из хранилища значений
Здесь главное ограничение типа. СУБД видит реквизит ХранилищеЗначения как непрозрачный набор байтов.
Что это значит на практике:
- 🚫 Поле нельзя использовать в условиях
ГДЕ, вУПОРЯДОЧИТЬ ПО, вСГРУППИРОВАТЬ ПОи в соединениях. - 🚫 По нему нельзя построить индекс и сделать отбор в динамическом списке.
- ✅ Поле можно выбрать в запросе, а затем вызвать
Получить()у значения в выборке.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка КАК Ссылка,
| Товары.Настройки КАК Настройки
|ИЗ
| Справочник.Товары КАК Товары
|ГДЕ
| Товары.ПометкаУдаления = ЛОЖЬ";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Таблица = Выборка.Настройки.Получить();
Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
Продолжить;
КонецЕсли;
Найденные = Таблица.НайтиСтроки(Новый Структура("Активна", Истина));
// обработка найденных строк
КонецЦикла;
Отбор по обычным реквизитам делается в запросе, а уже содержимое хранилища фильтруется в коде. После Получить() у вас обычная таблица значений: НайтиСтроки(), Найти(), Сортировать("Дата Убыв"), Скопировать() с отбором — всё работает как обычно.
Такой код читает и распаковывает хранилище у каждой записи. На 50 000 элементов с таблицами внутри это минуты и гигабайты памяти. Если по значению нужно искать регулярно — выносите его в отдельный реквизит или в регистр сведений.
Правило простое: всё, по чему ищут, сортируют или строят отчёты, должно жить в обычных полях. В хранилище кладут то, что читают целиком и по одной записи.
Как сравнить два хранилища
Оператор = для двух объектов ХранилищеЗначения не сравнивает содержимое. Сравнивают извлечённые данные:
- для двоичных данных — хеш-сумму через
ХешированиеДанных; - для коллекций и структур — строковое представление через
ЗначениеВСтрокуВнутр().
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.Добавить(Хранилище1.Получить());
Хеш1 = Хеш.ХешСумма;
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.Добавить(Хранилище2.Получить());
Хеш2 = Хеш.ХешСумма;
ОдинаковыеФайлы = (Хеш1 = Хеш2);
Этот пример — для хранилищ с двоичными данными внутри. Хеш удобно сохранить в отдельный строковый реквизит при записи файла: тогда поиск дубликатов файлов делается обычным запросом.
Хранилище значений и другие способы хранить данные
Выбор зависит от того, сколько живут данные и нужен ли по ним поиск.
| Механизм | Где живут данные | Срок жизни | Поиск в запросе | Когда брать |
|---|---|---|---|---|
ХранилищеЗначения в реквизите | В таблице объекта в базе | Постоянно | Нет | Вложение или настройка одного объекта |
Временное хранилище (ПоместитьВоВременноеХранилище) | В памяти сеанса | До закрытия формы или сеанса | Нет | Передача файла между клиентом и сервером |
ДвоичныеДанные | В памяти | Пока жива переменная | Нет | Работа с файлом в коде |
ТаблицаЗначений, Структура | В памяти | Пока жива переменная | Методы коллекции | Обработка данных в коде |
| Регистр сведений с обычными полями | В базе | Постоянно | Да | Данные, по которым нужен отбор и отчёты |
ХранилищеОбщихНастроек | В базе, по пользователю | Постоянно | Нет | Пользовательские настройки форм и обработок |
| Присоединённые файлы (БСП) | В базе или в томах на диске | Постоянно | По описанию файла | Документы и сканы в типовых конфигурациях |
В типовых конфигурациях на БСП для файлов есть подсистема «Работа с файлами», и она умеет хранить их в томах на диске. Если вы дорабатываете типовую, лучше использовать её, а не заводить свой реквизит-хранилище: база не раздувается, а файлы видны в общем списке вложений.
Частые ошибки с хранилищем значений и как их избежать
Ссылки внутри хранилища не видит поиск ссылок. Если в хранилище лежит таблица со ссылками на контрагентов, при удалении помеченных объектов платформа не узнает, что они где-то используются. Элемент удалится, в таблице останутся «битые» ссылки. Ключевые ссылки дублируйте в обычной табличной части, если их удаление критично.
База растёт на десятки гигабайт. Сканы в высоком разрешении в реквизите справочника дают таблицу, которая медленно копируется, выгружается в .dt и долго восстанавливается. Перед загрузкой картинок уменьшайте их, а большие файлы держите в томах.
Получить() вызывают в цикле много раз. Каждый вызов — заново десериализация. Достаньте значение один раз в переменную и работайте с ней.
Нет проверки типа после Получить(). Старые записи могут содержать Неопределено или значение другого формата. Проверяйте ТипЗнч() до обращения к колонкам, иначе код упадёт на первой же «пустой» записи.
Сжимают уже сжатое. СжатиеДанных(9) для JPEG или ZIP не даёт выигрыша, но нагружает сервер при каждой записи.
Кладут в хранилище то, что потом нужно в отчётах. Через полгода приходит задача «покажите все товары, у которых в настройках стоит флаг», и выясняется, что запросом её не решить. Решайте заранее, какие поля могут понадобиться для отбора.