Получаем содержимое реквизита «Хранилище значения»: три рабочих способа

· 9 мин

Содержание

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

Главная причина, по которой возникает вопрос «как извлечь» — поле хранилища нельзя прочитать запросом или напрямую из таблицы СУБД. Платформа хранит значение в своём внутреннем сериализованном формате, часто сжатым, поэтому получить исходный файл можно только средствами встроенного языка. Ниже — как получить двоичные данные из хранилища значений 1С тремя способами, чем они отличаются и на чём обычно спотыкаются.

Почему реквизит нельзя прочитать запросом или из таблицы базы

Реквизит типа «Хранилище значения» — это контейнер. При записи платформа сериализует положенный объект во внутреннее представление и, если включено сжатие, упаковывает его. В таблице СУБД лежит именно этот упакованный блок, а не исходный PDF, картинка или XML.

Отсюда два практических следствия:

  • 🔒 Запросом содержимое не достать. Поле такого типа можно выбрать в запросе, но обратиться к тому, что внутри, язык запросов не умеет. Использовать его в условиях ГДЕ и в сортировке тоже нельзя.
  • 🧩 Сторонними средствами по SQL — ненадёжно. Формат внутренний и недокументированный, с заголовком и сжатием. Попытка распарсить blob в обход платформы ломается на первой же смене версии. Единственный надёжный путь — встроенный язык 1С и метод Получить().

Поэтому любое извлечение начинается одинаково: получили объект из хранилища, определили его тип, дальше работаем с двоичными данными.

Как двоичные данные попадают в хранилище

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

// Читаем файл с диска в двоичные данные
Данные = Новый ДвоичныеДанные("C:\вход\скан.pdf");

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

Два момента, которые потом всплывают при чтении:

  • 🧱 Тип при чтении равен типу при записи. Положили ДвоичныеДанные — получите ДвоичныеДанные. Положили строку или Base64 — получите строку. Из-за рассинхрона типов и выходят «битые» файлы.
  • 🗜️ Сжатие задаётся при записи. Второй параметр Новый СжатиеДанных() уменьшает размер в базе; на чтение это не влияет — Получить() распакует сам. Степень сжатия (0–9) задаётся в конструкторе сжатия.

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

Базовый приём: метод Получить() и проверка типа

Получить() возвращает то, что сохраняли. Если в хранилище положили ДвоичныеДанные — вернётся ДвоичныеДанные. Если положили Картинку — вернётся Картинка, и байты из неё достаются уже её методом. Пустое хранилище вернёт Неопределено.

Поэтому первым делом проверяют тип:

// Хранилище — значение реквизита вашего объекта
// (справочника, документа, регистра) типа «Хранилище значения».
Значение = Хранилище.Получить();

Если Значение = Неопределено Тогда
    Сообщить("Хранилище пустое, данных нет");
    Возврат;
КонецЕсли;

Если ТипЗнч(Значение) = Тип("ДвоичныеДанные") Тогда
    Данные = Значение;
ИначеЕсли ТипЗнч(Значение) = Тип("Картинка") Тогда
    Данные = Значение.ПолучитьДвоичныеДанные();
Иначе
    Сообщить("В хранилище не двоичные данные, а " + ТипЗнч(Значение));
    Возврат;
КонецЕсли;

Сообщить("Размер данных: " + Данные.Размер() + " байт");

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

Что вернёт Получить() в зависимости от того, что сохраняли:

Что лежит в хранилищеКак получить байты
ДвоичныеДанныеДанные = Хранилище.Получить() — готово
КартинкаДанные = Хранилище.Получить().ПолучитьДвоичныеДанные()
Таблица значений, структураПолучить() вернёт сам объект — это данные 1С, а не файл
Значение со сжатиемПолучить() распакует автоматически

Для картинок есть нюанс: чтобы показать её на форме, двоичные данные обычно кладут во временное хранилище сеанса и привязывают к реквизиту-картинке через адрес. Если же нужен файл — берите байты методом ПолучитьДвоичныеДанные() и дальше действуйте как в способах 1 и 2.

Дальше у вас на руках объект ДвоичныеДанные — и его можно либо выгрузить в файл, либо обработать потоком.

Способ 1. Записать двоичные данные во временный файл

Самый простой и универсальный путь: получить данные и сразу сбросить их на диск. У ДвоичныеДанные для этого есть метод Записать().

Данные = Хранилище.Получить(); // ДвоичныеДанные

// Временный файл с нужным расширением
ИмяФайла = ПолучитьИмяВременногоФайла("pdf");
Данные.Записать(ИмяФайла);

Сообщить("Файл сохранён: " + ИмяФайла);

Когда этот способ уместен:

  1. Нужно отдать файл внешней программе, принтеру, почтовому вложению.
  2. Данные большие (архив, скан, видео) — на диске их удобнее передавать по частям.
  3. Файл сразу открывается ассоциированным приложением на клиенте.

Метод Записать() в серверном коде пишет на диск сервера, а не пользователя. Чтобы файл попал к пользователю, на клиенте применяют ПоместитьФайл или НачатьПолучениеФайлов — через временное хранилище сеанса. Не путайте «Хранилище значения» (тип реквизита в базе) и временное хранилище сеанса (ПоместитьВоВременноеХранилище) — это разные механизмы.

После работы временный файл стоит удалить УдалитьФайлы(ИмяФайла), иначе на сервере копятся «хвосты».

Если нужно выгрузить файлы пачкой — например, все вложения справочника в папку — тот же приём в цикле по выборке:

Выборка = Справочники.ВашСправочник.Выбрать();
Пока Выборка.Следующий() Цикл
    Значение = Выборка.ВашРеквизитХранилище.Получить();
    Если Значение = Неопределено Тогда
        Продолжить;
    КонецЕсли;
    ИмяФайла = "C:\выгрузка\" + Выборка.Код + ".dat";
    Значение.Записать(ИмяФайла);
КонецЦикла;

Расширение .dat здесь условное — если в базе хранится исходное имя файла, берите расширение из него, иначе определить тип содержимого из самих байтов платформа не обязана.

Способ 2. Читать потоком в памяти, без файла на диске

Если файл на диск класть не хочется — обрабатываете байты прямо в памяти. У ДвоичныеДанные есть метод ОткрытьПотокДляЧтения(), который возвращает поток. Поток принимают многие объекты платформы: чтение текста, парсеры XML/JSON, запись в HTTP-запрос.

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

Данные = Хранилище.Получить();
Поток = Данные.ОткрытьПотокДляЧтения();

ЧтениеТекста = Новый ЧтениеТекста(Поток, КодировкаТекста.UTF8);
Текст = ЧтениеТекста.Прочитать();
ЧтениеТекста.Закрыть();
Поток.Закрыть();

Второй частый приём «в памяти» — перевести данные в строку Base64, чтобы положить в XML, JSON или передать в веб-сервис:

Данные = Хранилище.Получить();
СтрокаBase64 = Base64Строка(Данные);
// обратно: Данные = Base64Значение(СтрокаBase64);

Чем способ хорош:

  • ⚡ Нет обращения к диску — быстрее и без мусорных файлов.
  • 🔁 Удобно для конвейера — поток сразу уходит в парсер или в тело запроса.
  • 🧵 Экономит память на больших данных, если читать поток порциями, а не целиком.

Когда нужно работать именно с байтами — посчитать контрольную сумму, собрать данные по частям, передать в функцию, ожидающую буфер, — двоичные данные переводят в буфер:

Данные = Хранилище.Получить();
Буфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Данные);
Сообщить("Байт в буфере: " + Буфер.Размер);
// обратно: Данные = ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Буфер);

Поток и ЧтениеТекста обязательно закрывайте. Незакрытый поток держит данные в памяти до конца процедуры и мешает повторному чтению.

Способ 3. Достать данные из внешней программы через COM-соединение

Иногда извлечь содержимое нужно снаружи — из служебного обработчика, скрипта выгрузки, другой базы. Тогда подключаются к информационной базе внешним соединением через V83.COMConnector и вызывают тот же Получить().

Коннектор = Новый COMObject("V83.COMConnector");
Соединение = Коннектор.Connect("Srvr=""servername"";Ref=""basename""");

Объект = Соединение.Справочники.ВашСправочник.НайтиПоКоду("000001");
Данные = Объект.ВашРеквизитХранилище.Получить();
Данные.Записать("C:\выгрузка\файл.bin");

Строку соединения собирают по типу базы:

  • 🖥️ Серверная база — Srvr="имя_сервера";Ref="имя_базы".
  • 📁 Файловая база — File="C:\путь\к\базе".
  • 🔑 Пользователь и пароль добавляют параметрами Usr="..." и Pwd="...". Заводите под выгрузку отдельного пользователя с правами только на чтение нужных данных.

Что учесть при таком доступе:

  • Разрядность comcntr.dll должна совпадать с разрядностью программы, которая вызывает COM. Это частая причина ошибки Класс не зарегистрирован — библиотеку регистрируют regsvr32 из каталога нужной платформы.
  • Через внешнее соединение доступны и русские, и английские имена методов платформы (Получить / Get), названия конкретных реквизитов зависят от вашей конфигурации.
  • Соединение после работы освобождают: Соединение = Неопределено.

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

Частые ошибки при извлечении данных из хранилища

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

Ещё одна типовая путаница — попытка достать содержимое запросом или прочитать поле напрямую из СУБД. Это не ошибка кода, а неверная модель: содержимое хранилища живёт только через Получить().

Какой способ выбрать

Короткий ориентир под задачу:

СпособКогда выбратьМинус
Временный файл (способ 1)Нужен файл для внешней программы, печати, вложенияПишет на диск, требует уборки
Поток в памяти (способ 2)Парсинг, Base64, передача в веб-сервисНужно аккуратно закрывать потоки
COM-соединение (способ 3)Выгрузка снаружи, из другой базы или скриптаРасход лицензии, зависит от разрядности dll

Правило простое: если данные остаются внутри 1С и идут в парсер — работайте потоком; если файл нужен «наружу» — пишите во временный файл; доступ из стороннего процесса — только COM. Во всех трёх случаях сердцевина одна и та же: Получить() и проверка типа.

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

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