ХранилищеЗначения в 1С 8.3: запись таблиц, структур и файлов в реквизит
Содержание
Чтобы поместить данные в хранилище значений 1С, создайте объект конструктором Новый ХранилищеЗначения(Значение, Новый СжатиеДанных(9)), присвойте его реквизиту типа «Хранилище значений» и запишите объект. Достать значение обратно — метод Получить(). Весь код выполняется на сервере: в тонком клиенте этого типа нет.
Дальше по шагам: что можно хранить, как записать таблицу, дерево и файл, как вывести хранилище в управляемую форму, как передать его в другую базу и на чём обычно спотыкаются.
Что такое хранилище значений в 1С и когда оно нужно
ХранилищеЗначения — тип платформы 1С:Предприятие 8.3, который упаковывает почти любое значение во внутренний сериализованный формат. В базе такой реквизит хранится как двоичные данные неограниченной длины.
Зачем это нужно на практике:
- 📦 хранить настройки обработки или пользователя целой таблицей, а не десятком реквизитов;
- 🖼️ держать картинку, скан или PDF прямо в карточке справочника;
- 🌳 сохранять сложную структуру (дерево, соответствие, вложенные массивы) без отдельных табличных частей;
- 🧾 класть в документ «снимок» данных на момент проведения, например готовый табличный документ печатной формы.
Цена удобства — хранилище «непрозрачно» для запросов. По его содержимому нельзя отобрать, отсортировать или сгруппировать записи. Поле можно только выбрать и потом распаковать в коде.
Если данные нужно искать или фильтровать, храните их в обычных реквизитах или табличной части. Хранилище подходит для того, что читают целиком.
Не путайте два похожих механизма:
| Механизм | Где живут данные | Срок жизни | Для чего |
|---|---|---|---|
ХранилищеЗначения | в реквизите объекта в базе | пока не перезапишете | постоянное хранение |
Временное хранилище (ПоместитьВоВременноеХранилище) | в памяти сеанса, по адресу-строке | до закрытия формы или сеанса | передача между клиентом и сервером |
ХранилищеОбщихНастроек | в системной таблице настроек | постоянно | настройки пользователя по ключу |
Как поместить данные в хранилище значений: базовый код
Сначала в конфигураторе добавьте реквизит. Откройте объект метаданных (справочник, документ или регистр сведений
), на вкладке реквизитов создайте новый и выберите тип Хранилище значений. После этого обновите конфигурацию базы данных.
Запись в реквизит справочника
&НаСервере
Процедура СохранитьНастройкиНаСервере(СсылкаНаЭлемент, ТаблицаНастроек)
ОбъектЭлемента = СсылкаНаЭлемент.ПолучитьОбъект();
ОбъектЭлемента.Настройки = Новый ХранилищеЗначения(ТаблицаНастроек, Новый СжатиеДанных(9));
ОбъектЭлемента.Записать();
КонецПроцедуры
Три обязательных шага: получить объект, присвоить новый экземпляр хранилища, записать объект. Пропустите любой — данные не сохранятся.
Чтение обратно
Значение = СсылкаНаЭлемент.Настройки.Получить();
Если ТипЗнч(Значение) = Тип("ТаблицаЗначений") Тогда
// работаем с таблицей
КонецЕсли;
Проверка ТипЗнч спасает от ошибки, когда реквизит ещё пустой. Пустое хранилище возвращает Неопределено.
Параметр сжатия
Второй параметр конструктора необязательный. Уровень задаётся числом от 0 до 9 в Новый СжатиеДанных(N):
0— без сжатия, быстрее запись;9— максимальное сжатие, меньше размер базы;- без параметра — данные не сжимаются.
Таблицы, тексты, XML сжимаются в разы. JPEG, PNG, ZIP и PDF со сжатыми потоками почти не уменьшаются: тратить на них процессор смысла мало.
Запись в регистр сведений
МенеджерЗаписи = РегистрыСведений.ДанныеОбработок.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Ключ = "НастройкиВыгрузки";
МенеджерЗаписи.Значение = Новый ХранилищеЗначения(Настройки, Новый СжатиеДанных(9));
МенеджерЗаписи.Записать(Истина);
Здесь ДанныеОбработок — пример регистра с измерением Ключ (строка) и ресурсом Значение (хранилище значений). Параметр Истина у Записать замещает существующую запись с тем же ключом.
Какие данные можно помещать в хранилище, а какие нет
В хранилище попадает всё, что платформа умеет сериализовать во внутренний формат. Если в составе значения есть хотя бы один несериализуемый элемент, конструктор вызовет исключение.
✅ Можно:
- примитивы:
Строка,Число,Дата,Булево; - ссылки на объекты базы (
СправочникСсылка,ДокументСсылкаи т. д.); Массив,Структура,Соответствиеи их фиксированные варианты;ТаблицаЗначений,ДеревоЗначений,СписокЗначений;ДвоичныеДанные,Картинка,ТабличныйДокумент;- другое
ХранилищеЗначения(вложенность допустима).
❌ Нельзя:
Запрос,РезультатЗапроса, выборки иМенеджерВременныхТаблиц;COMОбъекти объекты внешних компонент;- потоки и объекты чтения/записи:
ЧтениеXML,ЗаписьXML,ЧтениеJSON; - формы и элементы форм.
Самая частая ловушка — «грязная» структура. Например, в Структуру настроек случайно попал РезультатЗапроса, и запись падает на, казалось бы, безобидной строке. Перед упаковкой выгружайте результат запроса в таблицу: Результат.Выгрузить().
Ссылки внутри хранилища записываются по уникальному идентификатору. Если элемент потом удалят, при распаковке вы получите битую ссылку <Объект не найден>. Ссылочная целостность и поиск ссылок на такие данные не распространяются.
Примеры: запись таблиц, деревьев и вложенных структур в хранилище значений
Таблица значений
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Таблица.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
НоваяСтрока = Таблица.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00-00000001");
НоваяСтрока.Количество = 15;
ОбъектДокумента.СнимокОстатков = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));
Типы колонок сохраняются вместе с таблицей. После Получить() вы получите ту же таблицу с теми же колонками.
Дерево значений
Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("Раздел", Новый ОписаниеТипов("Строка"));
Дерево.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
Корень = Дерево.Строки.Добавить();
Корень.Раздел = "Расходы";
Ветка = Корень.Строки.Добавить();
Ветка.Раздел = "Аренда";
Ветка.Сумма = 120000;
Ветка = Корень.Строки.Добавить();
Ветка.Раздел = "Связь";
Ветка.Сумма = 8500;
Корень.Сумма = 128500;
ОбъектОтчета.Структура = Новый ХранилищеЗначения(Дерево);
Иерархия сохраняется полностью, на любую глубину. Итог по корню здесь проставлен вручную: 120 000 + 8 500 = 128 500.
Вложенная структура
Параметры = Новый Структура;
Параметры.Вставить("ВерсияФормата", 2);
Параметры.Вставить("Организации", Новый Массив);
Параметры.Организации.Добавить(ОрганизацияСсылка);
Параметры.Вставить("Отбор", Новый Соответствие);
Параметры.Отбор.Вставить("Склад", СкладСсылка);
Параметры.Вставить("Строки", Таблица);
ОбъектОбработки.Параметры = Новый ХранилищеЗначения(Параметры, Новый СжатиеДанных(9));
💡 Поле ВерсияФормата — простой приём практика. Когда через полгода состав структуры поменяется, код чтения по номеру версии поймёт, какие ключи ждать, и не упадёт на старых записях.
Файл с диска пользователя
На клиенте файл помещается во временное хранилище, а на сервере уже перекладывается в постоянное:
&НаКлиенте
Процедура ЗагрузитьФайл(Команда)
Оповещение = Новый ОписаниеОповещения("ЗагрузитьФайлЗавершение", ЭтотОбъект);
НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ЗагрузитьФайлЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Результат Тогда
ЗаписатьФайлНаСервере(Адрес);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ЗаписатьФайлНаСервере(Адрес)
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(Адрес);
ОбъектЭлемента = РеквизитФормыВЗначение("Объект");
ОбъектЭлемента.Файл = Новый ХранилищеЗначения(ДвоичныеДанныеФайла);
ОбъектЭлемента.Записать();
ЗначениеВРеквизитФормы(ОбъектЭлемента, "Объект");
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
Сжатие для файла не указано намеренно: сканы и фотографии уже сжаты. Для текстовых файлов и выгрузок XML добавьте Новый СжатиеДанных(9).
Хранилище значений в управляемой форме
Реквизит типа хранилища в основном реквизите формы Объект на клиент не передаётся. Поэтому значение распаковывают на сервере в отдельный реквизит формы и упаковывают обратно перед записью.
Порядок настройки:
- В форме объекта создайте реквизит формы, например
ТаблицаНастроектипаТаблицаЗначений. - Добавьте ему колонки с теми же именами и типами, что у упаковываемой таблицы.
- Выведите реквизит на форму элементом «Таблица».
- Добавьте два серверных обработчика формы.
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
Значение = ТекущийОбъект.Настройки.Получить();
Если ТипЗнч(Значение) = Тип("ТаблицаЗначений") Тогда
ЗначениеВРеквизитФормы(Значение, "ТаблицаНастроек");
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Таблица = РеквизитФормыВЗначение("ТаблицаНастроек");
ТекущийОбъект.Настройки = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));
КонецПроцедуры
ТекущийОбъект в этих обработчиках — настоящий объект базы, у него хранилище доступно. Записывать его вручную не нужно: платформа запишет сама после ПередЗаписьюНаСервере.
Если пользователь поменял только таблицу, а форма не предлагает сохранить изменения, поставьте Модифицированность = Истина в обработчике ПриИзменении элемента таблицы.
Обмен данными между базами через хранилище значений
Хранилище удобно гонять между базами: одно поле переносит целую таблицу или файл. Для этого его переводят в строку и обратно.
Через XML-строку (Base64)
// В базе-источнике
СтрокаДанных = XMLСтрока(Хранилище);
// В базе-приёмнике
Хранилище = XMLЗначение(Тип("ХранилищеЗначения"), СтрокаДанных);
Данные = Хранилище.Получить();
XMLСтрока выдаёт Base64 от внутреннего представления. Такую строку можно положить в XML-пакет, в тело HTTP-запроса или в файл.
Через сериализатор XDTO
Если обмен уже идёт через СериализаторXDTO, отдельной обработки не нужно: реквизит-хранилище выгружается и загружается вместе с объектом как обычное поле.
Главная проблема: ссылки внутри
Ссылки в хранилище не перекодируются правилами обмена. Правила конвертации видят поле как один кусок двоичных данных и не заглядывают внутрь.
Что из этого следует:
- 🔗 если базы обмениваются с сохранением уникальных идентификаторов, ссылки в приёмнике найдут свои объекты;
- 🧩 если идентификаторы разные, в приёмнике вместо номенклатуры и контрагентов будет
<Объект не найден>.
Надёжный приём: перед упаковкой заменить ссылки на идентифицирующие данные (код, ИНН, артикул или Строка(Ссылка.УникальныйИдентификатор())), а в приёмнике найти объекты заново.
Для Каждого СтрокаТаблицы Из Таблица Цикл
СтрокаТаблицы.КодНоменклатуры = СтрокаТаблицы.Номенклатура.Код;
КонецЦикла;
Таблица.Колонки.Удалить("Номенклатура");
Колонку КодНоменклатуры (строка) нужно заранее добавить в таблицу. После обмена в приёмнике по коду ищут элемент через НайтиПоКоду.
Как ускорить работу с хранилищем значений в больших базах
На паре сотен записей хранилище незаметно. На десятках тысяч элементов с файлами внутри база начинает тормозить. Что помогает:
- Не читайте объект целиком ради одного поля. Обращение
Ссылка.РеквизитиПолучитьОбъект()тянут все реквизиты, включая тяжёлое хранилище. Нужны данные — выбирайте поле запросом, не нужны — не включайте его вВЫБРАТЬ. - Выносите тяжёлое в отдельный регистр сведений. Справочник хранит только описание, а двоичные данные лежат в регистре с измерением-ссылкой на элемент. Так устроено хранение файлов в типовых конфигурациях на БСП: список файлов открывается быстро, содержимое читается по требованию.
- Сжимайте только то, что сжимается. Уровень 9 для таблиц и текстов, без сжатия — для картинок и архивов.
- Не храните в одном поле то, что меняется по частям. Любое изменение строки таблицы перезаписывает хранилище целиком. Если пользователи правят данные построчно, табличная часть будет быстрее.
- Следите за размером базы. Хранилища — частая причина «распухания» таблиц справочников. Рост файловой базы на гигабайты без видимых причин стоит проверить запросом по объектам с таким реквизитом.
Запрос, который выбирает только нужное:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Файлы.Ссылка,
| Файлы.Наименование
|ИЗ
| Справочник.ПрисоединенныеФайлы КАК Файлы
|ГДЕ
| Файлы.Владелец = &Владелец";
Поле хранилища в выборку не попало — платформа его и не читает. Содержимое конкретного файла получают вторым запросом, когда пользователь его откроет.
Ошибки при записи в хранилище значений и как их обойти
Новый ХранилищеЗначения выполняется на тонком клиенте или в веб-клиенте. Перенесите его в процедуру с директивой &НаСервере или в серверный общий модуль.Получить() возвращает копию. Её изменения в реквизит не попадают. После правки снова присвойте Объект.Реквизит = Новый ХранилищеЗначения(Таблица) и запишите объект.Результат.Выгрузить().ПриЧтенииНаСервере в реквизит формы и упакуйте обратно в ПередЗаписьюНаСервере.Ещё одна неочевидная деталь: изменение одного хранилища внутри регистра сведений через набор записей перезаписывает весь набор по отбору. Если пишете набором, сначала прочитайте его (Прочитать()), иначе соседние записи с тем же отбором пропадут.
Частые вопросы
Есть ли ограничение на размер хранилища значений?
Как проверить, что хранилище пустое?
Получить() и сравните результат с Неопределено. Новый объект с незаполненным реквизитом возвращает именно Неопределено.Можно ли открыть содержимое хранилища без кода?
Объект.Реквизит.Получить() и раскройте результат.Чем хранилище значений отличается от реквизита типа ДвоичныеДанные?
ДвоичныеДанные нельзя выбрать для реквизита объекта метаданных. Чтобы сохранить файл в базе, его оборачивают в хранилище: Новый ХранилищеЗначения(ДвоичныеДанные).