Чтение реквизита типа ХранилищеЗначения: код для сервера, формы и файлов

· 9 мин

Содержание

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

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

Как устроено хранилище значений и чем оно отличается от похожих механизмов

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

Где встречается такой тип:

  • 📁 реквизиты справочников, документов, планов видов характеристик;
  • 📋 реквизиты табличных частей;
  • 📊 ресурсы и реквизиты регистров сведений;
  • 🧩 реквизиты обработок и отчётов (но не реквизиты управляемой формы).

Что можно положить внутрь: ДвоичныеДанные, Картинка, ТаблицаЗначений, ДеревоЗначений, Массив, Структура, Соответствие, СписокЗначений, ссылки на объекты, примитивные типы. Что нельзя: объекты, которые нельзя сериализовать, — Запрос, ЗаписьXML, COM-объекты, объекты справочников и документов (вместо объекта кладут ссылку).

В платформе есть ещё несколько «хранилищ» с похожими названиями. Их часто путают:

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

Если в коде конфигурации встретилось слово «хранилище», сначала определите, о каком из четырёх механизмов речь. От этого зависит весь дальнейший код.

Когда хранилище уместно, а когда нет:

  • ✅ настройки и параметры, которые читают целиком по одному ключу;
  • ✅ вложения, снимки данных, кэши расчётов, пакеты обмена;
  • ❌ данные, по которым нужен отбор, поиск или отчёт: содержимое хранилища невидимо для запросов и СКД;
  • ❌ часто меняющиеся строки, которые правят по одной: каждое изменение перезаписывает весь контейнер.

Если по содержимому хранилища понадобился отчёт, это сигнал вынести данные в табличную часть или регистр сведений .

Базовый код: получить данные из хранилища и положить их обратно

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

// Запись: кладём структуру в реквизит объекта
Параметры = Новый Структура("Ставка, Период", 20, '20260101');
СправочникОбъект.НастройкиРасчета = Новый ХранилищеЗначения(Параметры, Новый СжатиеДанных(9));
СправочникОбъект.Записать();

// Чтение: достаём ту же структуру
Параметры = СправочникОбъект.НастройкиРасчета.Получить();
Если Параметры = Неопределено Тогда
    Параметры = Новый Структура("Ставка, Период", 0, Дата(1, 1, 1));
КонецЕсли;

Три правила, которые экономят время на отладке:

  1. Получить() возвращает копию. Изменили полученную таблицу — в базе ничего не поменялось. Чтобы сохранить правку, создайте новый ХранилищеЗначения и присвойте его реквизиту.
  2. Пустое хранилище возвращает Неопределено. Новый объект, ни разу не записанный реквизит — всё это Неопределено. Проверяйте результат до обращения к его свойствам.
  3. Сжатие СжатиеДанных(9) — максимальная степень. Для картинок в формате PNG или JPG выигрыша почти нет, они уже сжаты. Для таблиц и XML сжатие заметно уменьшает размер базы.

Тип содержимого хранилище не помнит в явном виде. Если в один реквизит в разное время клали разные значения, проверяйте тип после чтения: Если ТипЗнч(Данные) = Тип("ТаблицаЗначений") Тогда.

Получение данных из реквизита документа, справочника и регистра

Способ зависит от того, что у вас на руках: объект, ссылка или ключ записи регистра.

Из объекта

Если объект уже прочитан, всё просто: ДокументОбъект.Вложение.Получить(). Так работает код модуля объекта, обработчики ПередЗаписью, ОбработкаПроведения.

По ссылке через точку

На сервере допустимо ДокументСсылка.Вложение.Получить(). Код короткий, но платформа читает объект целиком, со всеми табличными частями. Для одного документа это не страшно, в цикле по тысяче документов — заметно медленно.

Запросом

Для массового чтения подходит запрос. Поле с типом ХранилищеЗначения можно выбирать, но нельзя использовать в условиях ГДЕ, в УПОРЯДОЧИТЬ ПО, СГРУППИРОВАТЬ ПО и в РАЗЛИЧНЫЕ.

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Док.Ссылка КАК Ссылка,
|    Док.Вложение КАК Вложение
|ИЗ
|    Документ.ПоступлениеТоваровУслуг КАК Док
|ГДЕ
|    Док.Дата МЕЖДУ &Начало И &Конец";
Запрос.УстановитьПараметр("Начало", НачалоМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Конец", КонецМесяца(ТекущаяДатаСеанса()));

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

Имя документа и реквизита Вложение здесь условные — подставьте свои. В типовых конфигурациях реквизитов с хранилищем у документов немного, чаще это ваши доработки или расширения.

Из регистра сведений

Для одной записи удобен менеджер записи:

МенеджерЗаписи = РегистрыСведений.ДанныеОбмена.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Узел = УзелОбмена;
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
    Пакет = МенеджерЗаписи.Данные.Получить();
КонецЕсли;

Выбран() показывает, нашлась ли запись с такими значениями измерений. Без этой проверки вы получите пустое хранилище и Неопределено на выходе, а причину будете искать долго. Регистр и его измерения в примере условные.

Если в конфигурации внедрена БСП, для чтения одного реквизита по ссылке есть ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Вложение"). Функция вернёт само хранилище, а не содержимое: Получить() всё равно нужен.

Через внешнее соединение

Из другой программы или другой базы хранилище читают через COM-соединение. Тип там доступен, но метод Получить() вызывается на стороне подключённой базы, а результат приходит COM-объектом. Простые значения (строки, числа, даты) передаются нормально. Таблицу значений удобнее сначала превратить в строку в самой базе через ЗначениеВСтрокуВнутр() в общем модуле с флагом «Внешнее соединение», а в принимающей базе восстановить через ЗначениеИзСтрокиВнутр(). Это работает между базами на одной платформе 8.3; для обмена с другими системами надёжнее XML или JSON.

Таблицы значений, деревья и массивы в хранилище

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

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

// Прочитать и дописать строку
Таблица = Объект.ПрайсЛист.Получить();
Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
    Таблица = Новый ТаблицаЗначений;
    Таблица.Колонки.Добавить("Номенклатура");
    Таблица.Колонки.Добавить("Цена");
КонецЕсли;
НоваяСтрока = Таблица.Добавить();
НоваяСтрока.Номенклатура = Номенклатура;
НоваяСтрока.Цена = 150;
Объект.ПрайсЛист = Новый ХранилищеЗначения(Таблица, Новый СжатиеДанных(9));

Что важно знать про таблицы и деревья:

  • 🔑 Ссылки сохраняются как ссылки. Если элемент справочника потом удалят, в таблице останется битая ссылка. Перед использованием проверяйте её, например, через ОбщегоНазначения.СсылкаСуществует() в БСП или запросом.
  • 🧱 Структура колонок восстанавливается. Имена, типы, индексы приезжают вместе с данными.
  • 🌳 Дерево значений читается так же, через Получить(). Обходите его рекурсивно по Строки.
  • 📦 Массив и структура восстанавливаются целиком, вложенность любая.

Показать такую таблицу на управляемой форме напрямую нельзя. Создайте на форме реквизит типа ТаблицаЗначений с нужными колонками и заполните его на сервере через ЗначениеВРеквизитФормы(Таблица, "ТаблицаНаФорме"). Обратное преобразование для записи — РеквизитФормыВЗначение("ТаблицаНаФорме").

Выгрузка в файл и загрузка из файла

Файлы кладут в хранилище в виде ДвоичныеДанные. На сервере это делается в две строки.

// Файл с диска сервера → в реквизит
ДД = Новый ДвоичныеДанные("C:\Обмен\договор.pdf");
Объект.Скан = Новый ХранилищеЗначения(ДД, Новый СжатиеДанных(9));

// Из реквизита → в файл на сервере
ДД = Объект.Скан.Получить();
Если ТипЗнч(ДД) = Тип("ДвоичныеДанные") Тогда
    ДД.Записать("C:\Обмен\договор_копия.pdf");
КонецЕсли;

Путь здесь — путь на компьютере, где выполняется серверный код. В клиент-серверном варианте это сервер 1С, а не компьютер пользователя. У службы сервера должны быть права на эту папку.

Если файл нужно отдать пользователю, путь лежит через временное хранилище:

  1. На сервере прочитайте ДвоичныеДанные из реквизита.
  2. Поместите их во временное хранилище: Адрес = ПоместитьВоВременноеХранилище(ДД, УникальныйИдентификатор).
  3. Верните адрес на клиент.
  4. На клиенте сохраните файл. В конфигурациях на БСП — ФайловаяСистемаКлиент.СохранитьФайл(Неопределено, Адрес, "договор.pdf").

Загрузка от пользователя идёт в обратную сторону. В БСП это ФайловаяСистемаКлиент.ЗагрузитьФайл, который возвращает адрес во временном хранилище. На сервере получаете данные ПолучитьИзВременногоХранилища(Адрес) и кладёте их в ХранилищеЗначения.

Большие файлы в реквизитах раздувают базу и замедляют чтение объекта через точку. Для вложений от 10–20 МБ и выше подумайте о хранении в томах на диске, как это делает подсистема «Работа с файлами» в БСП.

Тонкий, веб- и толстый клиент: где доступен тип

Это главная причина, по которой код «не работает ».

КонтекстДоступен ли ХранилищеЗначения
Сервер, в том числе &НаСервере, модули объектов и менеджеровДа
Толстый клиент, обычное приложениеДа
Внешнее соединение (COM)Да
Тонкий клиент, &НаКлиентеНет
Веб-клиентНет

Отсюда следствия для управляемых форм:

  • 🚫 Реквизит объекта типа ХранилищеЗначения не передаётся в данные формы. Обращение Объект.Скан на клиенте не даст содержимого.
  • ✅ Читайте его в ПриЧтенииНаСервере(ТекущийОбъект): там ТекущийОбъект — настоящий объект базы, и ТекущийОбъект.Скан.Получить() работает.
  • ✅ Записывайте в ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи): присвойте ТекущийОбъект.Скан = Новый ХранилищеЗначения(...).

Типичная задача — показать картинку из реквизита на форме:

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

АдресФото — реквизит формы типа Строка, к нему привязано поле картинки. Платформа сама подтянет изображение по адресу.

В толстом клиенте обычного приложения всё проще: ЭлементыФормы.Картинка.Картинка = Объект.Фото.Получить() работает прямо в модуле формы. Но новые доработки так писать не стоит: код перестанет работать при переходе на управляемые формы.

Ошибки при чтении хранилища и как их обработать

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

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

Если данные в хранилище могут быть повреждены (например, после ручной правки таблиц СУБД или сбоя при обмене), оберните чтение в обработку исключений:

Попытка
    Данные = Объект.Вложение.Получить();
Исключение
    ЗаписьЖурналаРегистрации("Чтение хранилища", УровеньЖурналаРегистрации.Ошибка,
        Объект.Метаданные(), Объект.Ссылка, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    Данные = Неопределено;
КонецПопытки;

Такой код не остановит групповую обработку из-за одного битого объекта, а причина останется в журнале регистрации.

Ещё одна ловушка — попытка прочитать хранилище прямо из СУБД. В таблице лежит сериализованный и, возможно, сжатый поток во внутреннем формате платформы. SQL-запросом его не разобрать — читайте данные только средствами 1С, например через внешнее соединение или HTTP-сервис.

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

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