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

· 9 мин

Содержание

Чтобы поместить данные в хранилище значений 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).

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

Реквизит типа хранилища в основном реквизите формы Объект на клиент не передаётся. Поэтому значение распаковывают на сервере в отдельный реквизит формы и упаковывают обратно перед записью.

Порядок настройки:

  1. В форме объекта создайте реквизит формы, например ТаблицаНастроек типа ТаблицаЗначений.
  2. Добавьте ему колонки с теми же именами и типами, что у упаковываемой таблицы.
  3. Выведите реквизит на форму элементом «Таблица».
  4. Добавьте два серверных обработчика формы.
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
    Значение = ТекущийОбъект.Настройки.Получить();
    Если ТипЗнч(Значение) = Тип("ТаблицаЗначений") Тогда
        ЗначениеВРеквизитФормы(Значение, "ТаблицаНастроек");
    КонецЕсли;
КонецПроцедуры

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

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

Если пользователь поменял только таблицу, а форма не предлагает сохранить изменения, поставьте Модифицированность = Истина в обработчике ПриИзменении элемента таблицы.

Обмен данными между базами через хранилище значений

Хранилище удобно гонять между базами: одно поле переносит целую таблицу или файл. Для этого его переводят в строку и обратно.

Через XML-строку (Base64)

// В базе-источнике
СтрокаДанных = XMLСтрока(Хранилище);

// В базе-приёмнике
Хранилище = XMLЗначение(Тип("ХранилищеЗначения"), СтрокаДанных);
Данные = Хранилище.Получить();

XMLСтрока выдаёт Base64 от внутреннего представления. Такую строку можно положить в XML-пакет, в тело HTTP-запроса или в файл.

Через сериализатор XDTO

Если обмен уже идёт через СериализаторXDTO, отдельной обработки не нужно: реквизит-хранилище выгружается и загружается вместе с объектом как обычное поле.

Главная проблема: ссылки внутри

Ссылки в хранилище не перекодируются правилами обмена. Правила конвертации видят поле как один кусок двоичных данных и не заглядывают внутрь.

Что из этого следует:

  • 🔗 если базы обмениваются с сохранением уникальных идентификаторов, ссылки в приёмнике найдут свои объекты;
  • 🧩 если идентификаторы разные, в приёмнике вместо номенклатуры и контрагентов будет <Объект не найден>.

Надёжный приём: перед упаковкой заменить ссылки на идентифицирующие данные (код, ИНН, артикул или Строка(Ссылка.УникальныйИдентификатор())), а в приёмнике найти объекты заново.

Для Каждого СтрокаТаблицы Из Таблица Цикл
    СтрокаТаблицы.КодНоменклатуры = СтрокаТаблицы.Номенклатура.Код;
КонецЦикла;
Таблица.Колонки.Удалить("Номенклатура");

Колонку КодНоменклатуры (строка) нужно заранее добавить в таблицу. После обмена в приёмнике по коду ищут элемент через НайтиПоКоду.

Как ускорить работу с хранилищем значений в больших базах

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

  1. Не читайте объект целиком ради одного поля. Обращение Ссылка.Реквизит и ПолучитьОбъект() тянут все реквизиты, включая тяжёлое хранилище. Нужны данные — выбирайте поле запросом, не нужны — не включайте его в ВЫБРАТЬ.
  2. Выносите тяжёлое в отдельный регистр сведений. Справочник хранит только описание, а двоичные данные лежат в регистре с измерением-ссылкой на элемент. Так устроено хранение файлов в типовых конфигурациях на БСП: список файлов открывается быстро, содержимое читается по требованию.
  3. Сжимайте только то, что сжимается. Уровень 9 для таблиц и текстов, без сжатия — для картинок и архивов.
  4. Не храните в одном поле то, что меняется по частям. Любое изменение строки таблицы перезаписывает хранилище целиком. Если пользователи правят данные построчно, табличная часть будет быстрее.
  5. Следите за размером базы. Хранилища — частая причина «распухания» таблиц справочников. Рост файловой базы на гигабайты без видимых причин стоит проверить запросом по объектам с таким реквизитом.

Запрос, который выбирает только нужное:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Файлы.Ссылка,
|    Файлы.Наименование
|ИЗ
|    Справочник.ПрисоединенныеФайлы КАК Файлы
|ГДЕ
|    Файлы.Владелец = &Владелец";

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

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

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

Ещё одна неочевидная деталь: изменение одного хранилища внутри регистра сведений через набор записей перезаписывает весь набор по отбору. Если пишете набором, сначала прочитайте его (Прочитать()), иначе соседние записи с тем же отбором пропадут.

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

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