К статье
Практический материал

Разработка и Конфигурация · 11 мин

Как сохранить файл в хранилище значений 1С

Как сохранить файл в хранилище значений 1С: работа с ХранилищеЗначения, ДвоичныеДанные, ПолучитьИзВременногоХранилища. Примеры кода, ошибки, FAQ.

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

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

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

Подготовка реквизита типа «ХранилищеЗначения» в конфигураторе

Прежде чем писать код, нужно место для хранения. Откройте конфигуратор, найдите нужный объект (например, справочник «Номенклатура» или регистр сведений «ПрисоединенныеФайлы») и добавьте реквизит с типом «ХранилищеЗначения». Этот тип не имеет длины и не индексируется — платформа хранит содержимое в служебном формате, часто в отдельной таблице СУБД.

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

Рекомендуется сразу добавить сопутствующие реквизиты: имя файла (строка), расширение, размер, дата загрузки. Само хранилище этих сведений не содержит, и без них вы не сможете показать пользователю список файлов или восстановить исходное имя при выгрузке.

Запись файла в хранилище: серверный код

Базовый сценарий — файл уже лежит на диске сервера или пришёл из клиента через временное хранилище. Серверный код читает его в ДвоичныеДанные и сохраняет:

Функция СохранитьФайлВХранилище(СсылкаНаОбъект, АдресВоВременномХранилище, ИмяФайла) Экспорт

ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВоВременномХранилище);

Объект = СсылкаНаОбъект.ПолучитьОбъект();

Объект.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));

Объект.ИмяФайла = ИмяФайла;

Объект.РазмерФайла = ДвоичныеДанные.Размер();

Объект.Записать();

УдалитьИзВременногоХранилища(АдресВоВременномХранилище);

КонецФункции

Конструктор Новый ХранилищеЗначения принимает два параметра: сами данные и необязательный объект СжатиеДанных. Сжатие со степенью 9 уменьшает объём текстовых и офисных файлов, но почти бесполезно для jpeg, mp4 и zip — они уже сжаты. При чтении распаковка выполняется автоматически, указывать параметры сжатия повторно не нужно.

Если файл лежит на диске сервера, вместо временного хранилища используйте ДвоичныеДанные = Новый ДвоичныеДанные("C:\Files\dogovor.pdf"). Учтите, что путь должен существовать именно на сервере 1С, а не на компьютере пользователя — частая причина ошибки «файл не найден».

Порядок записи файла
  1. 1
    Добавьте реквизит типа «ХранилищеЗначения» и обновите конфигурацию БД
  2. 2
    На клиенте вызовите НачатьПомещениеФайлаВХранилище или ПоместитьФайл для получения адреса временного хранилища
  3. 3
    В серверной процедуре получите ДвоичныеДанные через ПолучитьИзВременногоХранилища
  4. 4
    Присвойте реквизиту Новый ХранилищеЗначения(ДвоичныеДанные)
  5. 5
    Вызовите Записать() и удалите данные из временного хранилища
  6. 6
    Проверьте: реквизит заполнен, метод Получить() возвращает данные того же размера

Передача файла с клиента на сервер

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

&НаКлиенте

Процедура ВыбратьФайл(Команда)

Описание = Новый ОписаниеОповещения("ПослеПомещенияФайла", ЭтотОбъект);

НачатьПомещениеФайлаВХранилище(Описание, , , Истина, УникальныйИдентификатор);

КонецПроцедуры

&НаКлиенте

Процедура ПослеПомещенияФайла(Результат, Адрес, ВыбранноеИмяФайла, ДопПараметры) Экспорт

Если Результат Тогда

СохранитьФайлНаСервере(Адрес, ВыбранноеИмяФайла);

КонецЕсли;

КонецПроцедуры

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

Чтение файла из хранилища и выгрузка на диск

Обратная операция выполняется методом Получить(). Он возвращает значение, помещённое в хранилище, — в нашем случае ДвоичныеДанные:

&НаСервере

Функция ПолучитьФайлИзХранилища(СсылкаНаОбъект) Экспорт

ДвоичныеДанные = СсылкаНаОбъект.ДанныеФайла.Получить();

Если ДвоичныеДанные = Неопределено Тогда

Возврат Неопределено;

КонецЕсли;

Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, Новый УникальныйИдентификатор);

КонецФункции

На клиенте адрес из временного хранилища передаётся в метод ПолучитьФайл или НачатьПолучениеФайлаИзХранилища, который сохраняет данные по указанному пользователем пути. Если нужно просто показать картинку в форме, достаточно присвоить адрес временного хранилища реквизиту формы с видом «Поле картинки» — запись на диск не требуется.

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

Типичные ошибки
01
Чтение файла с диска клиента в серверном коде — на сервере такого пути нет, возникает ошибка «файл не обнаружен»
02
Отсутствие проверки Получить() на Неопределено — падение кода при пустом реквизите
03
Повторное помещение в хранилище уже сжатых данных без нужды — лишний расход памяти и времени
04
Хранение файла без имени и расширения — при выгрузке невозможно восстановить исходный файл
05
Забытый вызов Записать() после присвоения реквизита — данные теряются молча
06
Накопление данных во временном хранилище без удаления — рост потребления памяти сервера

Хранилище значений против томов и внешних хранилищ

Хранилище значений — не единственный способ прикрепить файл в 1С. Выбор зависит от объёма данных, частоты доступа и требований к резервному копированию.

Сравнение вариантов хранения

ХранилищеЗначения в реквизите

Подходит для небольших файлов: картинки товаров, сканы, печати. Всё внутри базы, простой код, но рост размера БД и бэкапов.

Тома хранения файлов (БСП)

Файлы лежат в каталоге на диске, в базе только ссылки. Подходит для больших объёмов; требует отдельного резервного копирования каталога.

Регистр сведений с хранилищем

Отвязывает файлы от объекта-владельца, позволяет хранить несколько файлов и версии. Чуть сложнее код, гибче структура.

Внешнее объектное хранилище (S3 и аналоги)

Для очень больших объёмов и распределённых систем. Требует интеграции и не входит в стандартный бэкап 1С.

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

Проверка результата и отладка

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

Если файл после выгрузки «битый», проверьте три места: не записали ли вы в хранилище строку с путём вместо двоичных данных; не применялось ли двойное кодирование при передаче через веб-сервисы (Base64 поверх Base64); не потерялось ли расширение, из-за чего программа-просмотрщик не распознаёт формат.

Контрольные точки

Как проверить результат

Вызов Получить() у записанного реквизита
Норма

Возвращает ДвоичныеДанные, размер совпадает с исходным файлом

Тревожный признак

Возвращает Неопределено или данные другого типа

Выгрузка файла на диск и открытие
Норма

Файл открывается штатной программой без ошибок

Тревожный признак

Файл не открывается, нулевой размер или неверное расширение

Реквизиты имени и размера
Норма

Заполнены и соответствуют загруженному файлу

Тревожный признак

Пустые — при выгрузке имя файла восстановить нельзя

Ограничения и безопасная эксплуатация

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

При массовой загрузке файлов обрабатывайте их порциями в транзакциях разумного размера. Запись тысяч объектов с бинарными данными в одной транзакции может привести к росту журнала транзакций СУБД и блокировкам. Для первоначального переноса архивов файлов используйте фоновые задания с контролем ошибок по каждому элементу.

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

Итог

Что важно запомнить

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

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

Можно ли хранить в хранилище значений не файл, а другие данные?

Да. Хранилище принимает практически любые сериализуемые значения: строки, таблицы значений, структуры, картинки. Часто в нём сохраняют настройки отчётов и произвольные параметры. Ограничение — значение должно поддерживать сериализацию платформой; COM-объекты и ссылки на объекты других баз сохранить нельзя.

Как узнать размер файла, уже записанного в хранилище?

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

Почему после записи реквизит остаётся пустым?

Чаще всего забыт вызов Записать() после присвоения реквизита, либо запись выполняется в другой транзакции, которая откатывается из-за ошибки дальше по коду. Также проверьте, что работаете с объектом, полученным через ПолучитьОбъект(), а не пытаетесь присвоить значение реквизиту ссылки напрямую — у ссылки реквизиты доступны только для чтения.

Нужно ли указывать сжатие при чтении из хранилища?

Нет. Параметры сжатия задаются один раз при создании хранилища и сохраняются вместе с данными. Метод Получить() автоматически возвращает данные в исходном виде, независимо от того, применялось ли сжатие при записи.

Что делать, если файлов много и база сильно выросла?

Рассмотрите перенос вложений в тома хранения файлов (подсистема «Работа с файлами» БСП) или во внешнее хранилище. Для уже накопленных данных напишите обработку переноса: чтение из хранилища, запись файла в том, очистка реквизита — с выполнением порциями в фоновом задании и обязательной резервной копией перед стартом.

Можно ли записать файл в хранилище через COM-соединение из другой базы?

Да. Через COM-коннектор доступны те же типы: создайте ДвоичныеДанные через Новый COMОбъект с вызовом NewObject("ДвоичныеДанные", путь) в контексте соединения, затем присвойте реквизиту объект NewObject("ХранилищеЗначения", данные). Учитывайте, что передача больших файлов через COM медленнее прямой загрузки.