Файл в реквизите типа ХранилищеЗначения: запись, чтение и очистка
Содержание
Чтобы записать файл в Хранилище Значений 1С, получите его содержимое как ДвоичныеДанные, оберните в Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9)) и присвойте реквизиту объекта на сервере. Затем запишите объект. С клиента файл сначала передают во временное хранилище, а в реквизит кладут уже на сервере — в управляемой форме удобнее всего в обработчике ПередЗаписьюНаСервере.
Тема общая для платформы 1С:Предприятие 8.3 и не зависит от конфигурации: код одинаково работает в БП, ЗУП, УТ и в собственной разработке. Примеры даны для тонкого клиента и асинхронных методов платформы 8.3.18 и новее.
Как 1С хранит файл в реквизите ХранилищеЗначения
Реквизит справочника, документа или регистра не может иметь тип ДвоичныеДанные. Для файлов, картинок и любых сериализуемых значений есть тип ХранилищеЗначения. Платформа сериализует то, что в него положили, и пишет в таблицу базы одним полем неограниченной длины.
Что важно знать до написания кода:
- 📦 Внутри лежат только байты. Имя файла, расширение, дата и размер в хранилище не сохраняются. Для них заводят отдельные реквизиты рядом:
ИмяФайла,Расширение,РазмерФайла. - 🖥️ Тип живёт на сервере. В тонком клиенте и веб-клиенте
ХранилищеЗначениянедоступно. Реквизит этого типа не передаётся в форму, поэтомуОбъект.Файлна клиенте не прочитать и не заполнить. - 🗜️ Сжатие — по желанию. Второй параметр конструктора,
Новый СжатиеДанных(N), где N от 0 до 9. Без него данные пишутся как есть. - 🔍 В запросах поле ограничено. Его можно выбрать, но нельзя использовать в условиях, сортировке и группировке.
Где держать файлы: в объекте или отдельно
Главное архитектурное решение — куда поставить реквизит. Если положить файл прямо в карточку документа, он будет читаться каждый раз при открытии и записи этого документа. При обращении через точку (Ссылка.Реквизит) платформа тоже подтягивает объект целиком, вместе с тяжёлым полем.
| Вариант | Когда подходит | Минусы |
|---|---|---|
| Реквизит в самом объекте | 1 небольшой файл: логотип, скан подписи, шаблон до 1 МБ | Объект тяжелеет, растёт объём при каждом чтении |
| Отдельный регистр сведений (Владелец, ИмяФайла, Данные) | Много вложений к документу, файлы от 1 МБ | Нужен отдельный код записи и чтения |
| Файлы на диске, в базе только путь | Сотни мегабайт и гигабайты | Резервное копирование базы не покрывает файлы |
Типовые конфигурации на БСП идут по второму и третьему пути: данные файлов вынесены из карточки в отдельное хранилище, а для крупных объёмов есть хранение в томах на диске.
Порядок записи файла в хранилище значений
Вся цепочка от выбора файла пользователем до строки в базе выглядит так:
- На клиенте пользователь выбирает файл. Платформа отправляет его во временное хранилище и возвращает адрес — строку вида
e1cib/tempstorage/.... - Адрес сохраняют в реквизит формы (строку). Сам файл ещё не в базе.
- При записи объекта сервер достаёт данные:
ПолучитьИзВременногоХранилища(Адрес)возвращаетДвоичныеДанные. - Из двоичных данных создают
Новый ХранилищеЗначения(...)и присваивают реквизиту объекта. - Объект записывается. Временное хранилище освобождают после успешной записи.
Передавайте в метод помещения файла УникальныйИдентификатор формы. Тогда данные живут во временном хранилище, пока открыта форма. Без идентификатора они удаляются после завершения текущего серверного вызова, и на шаге 3 вы получите Неопределено.
Примеры кода записи файла для разных сценариев
Из формы объекта: выбор файла пользователем
В форме справочника заведите реквизит формы АдресФайла (тип Строка). У объекта метаданных — реквизиты Файл (ХранилищеЗначения) и ИмяФайла (Строка). Команда «Загрузить файл»:
&НаКлиенте
Асинх Процедура ЗагрузитьФайл(Команда)
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.Заголовок = "Выберите файл";
ОписаниеФайла = Ждать ПоместитьФайлНаСерверАсинх(, , ПараметрыДиалога, УникальныйИдентификатор);
Если ОписаниеФайла = Неопределено Или ОписаниеФайла.ПомещениеФайлаОтменено Тогда
Возврат;
КонецЕсли;
АдресФайла = ОписаниеФайла.Адрес;
Объект.ИмяФайла = ОписаниеФайла.СсылкаНаФайл.Имя;
Модифицированность = Истина;
КонецПроцедуры
Запись в реквизит — в обработчиках формы на сервере:
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресФайла) Тогда
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайла);
ТекущийОбъект.Файл = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресФайла) Тогда
УдалитьИзВременногоХранилища(АдресФайла);
АдресФайла = "";
КонецЕсли;
КонецПроцедуры
Ключевой момент — ТекущийОбъект. Это настоящий объект базы, а не данные формы, поэтому ему можно присвоить хранилище. Временное хранилище чистим после записи: если запись сорвётся, пользователь нажмёт «Записать» ещё раз, и файл не потеряется.
На платформе ниже 8.3.18 асинхронных методов с Ждать нет. Там используют НачатьПомещениеФайла с описанием оповещения. Серверная часть остаётся той же.
На сервере: файл уже лежит на диске
Обработка загрузки, регламентное задание или внешнее соединение работают на сервере, и временное хранилище не нужно:
Процедура ЗаписатьФайлВКарточку(Ссылка, ПолноеИмяФайла) Экспорт
ДвоичныеДанные = Новый ДвоичныеДанные(ПолноеИмяФайла);
ОписаниеФайла = Новый Файл(ПолноеИмяФайла);
ОбъектДанных = Ссылка.ПолучитьОбъект();
ОбъектДанных.Файл = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
ОбъектДанных.ИмяФайла = ОписаниеФайла.Имя;
ОбъектДанных.РазмерФайла = ДвоичныеДанные.Размер();
ОбъектДанных.Записать();
КонецПроцедуры
Путь указывается в файловой системе сервера 1С, а не компьютера пользователя. В клиент-серверном варианте C:\Обмен\скан.pdf ищется на машине с рабочим процессом. У пользователя, под которым запущена служба, должны быть права на чтение этой папки.
В отдельный регистр сведений
Создайте независимый регистр сведений
, например ФайлыДокументов: измерения Владелец и ИмяФайла, ресурс Данные типа ХранилищеЗначения. Запись одного вложения:
МенеджерЗаписи = РегистрыСведений.ФайлыДокументов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Владелец = ДокументСсылка;
МенеджерЗаписи.ИмяФайла = ИмяФайла;
МенеджерЗаписи.Данные = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
МенеджерЗаписи.Записать(Истина);
Параметр Истина замещает существующую запись с тем же владельцем и именем. Документ при этом не перезаписывается, его версия и движения не трогаются.
Несколько файлов за один раз
Если к документу прикладывают пачку сканов, разрешите множественный выбор и пишите каждый файл в регистр. Клиентская часть:
&НаКлиенте
Асинх Процедура ЗагрузитьНесколько(Команда)
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.МножественныйВыбор = Истина;
ПомещенныеФайлы = Ждать ПоместитьФайлыНаСерверАсинх(, , ПараметрыДиалога, УникальныйИдентификатор);
Если ПомещенныеФайлы = Неопределено Тогда
Возврат;
КонецЕсли;
Адреса = Новый Массив;
Имена = Новый Массив;
Для Каждого ОписаниеФайла Из ПомещенныеФайлы Цикл
Адреса.Добавить(ОписаниеФайла.Адрес);
Имена.Добавить(ОписаниеФайла.СсылкаНаФайл.Имя);
КонецЦикла;
ЗаписатьФайлыНаСервере(Адреса, Имена);
КонецПроцедуры
Серверная процедура проходит по массивам с одинаковым индексом и для каждой пары вызывает запись в регистр из примера выше. Там же удобно проверить размер: ДвоичныеДанные.Размер() возвращает число байт. Например, отказ для файлов больше 20 МБ — условие ДвоичныеДанные.Размер() > 20 * 1024 * 1024. Ограничение лучше поставить сразу, чем потом чистить базу.
Как назвать реквизиты, чтобы файл можно было вернуть
Набор служебных полей рядом с хранилищем, который окупается при первом же скачивании:
- 📄
ИмяФайла— строка до 255 символов, с расширением; - 🔢
РазмерФайла— число, байты до сжатия; - 📅
ДатаЗагрузкииАвтор— кто и когда приложил файл.
Без РазмерФайла любой отчёт о тяжёлых вложениях придётся строить через распаковку каждого файла.
Когда сжатие лишнее
Сжатие тратит процессор и почти ничего не даёт на уже сжатых форматах.
- ✅ Сжимать имеет смысл: XML, JSON, TXT, CSV, BMP, несжатые выгрузки.
- ❌ Без толку: JPG, PNG, PDF со сканами, ZIP, DOCX и XLSX (это тоже ZIP-архивы).
Для таких файлов пишите Новый ХранилищеЗначения(ДвоичныеДанные) без второго параметра.
Чтение и восстановление файла из хранилища значений
Метод Получить() возвращает то, что положили, — в нашем случае ДвоичныеДанные. Если реквизит пустой, вернётся Неопределено, поэтому тип проверяйте всегда.
Читать тяжёлое поле лучше запросом, а не через точку от ссылки:
&НаСервере
Функция АдресФайлаДляСкачивания()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Файл КАК Файл
|ИЗ
| Справочник.Договоры КАК Т
|ГДЕ
| Т.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
Возврат "";
КонецЕсли;
ДвоичныеДанные = Выборка.Файл.Получить();
Если ТипЗнч(ДвоичныеДанные) <> Тип("ДвоичныеДанные") Тогда
Возврат "";
КонецЕсли;
Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Асинх Процедура СкачатьФайл(Команда)
Адрес = АдресФайлаДляСкачивания();
Если Адрес = "" Тогда
Сообщить("Файл не прикреплён");
Возврат;
КонецЕсли;
Ждать ПолучитьФайлССервераАсинх(Адрес, Объект.ИмяФайла);
КонецПроцедуры
Справочник.Договоры замените на свой объект. Пользователь увидит стандартный диалог сохранения с подставленным именем файла.
Другие сценарии чтения:
- 💾 Сохранить на диск сервера:
ДвоичныеДанные.Записать("C:\Выгрузка\" + ИмяФайла). - 🖼️ Показать картинку в форме: положите адрес из
ПоместитьВоВременноеХранилищев строковый реквизит формы и сделайте его путём к данным поля картинки. Удобно сделать это вПриЧтенииНаСервере(ТекущийОбъект)черезТекущийОбъект.Файл.Получить(). - 🔁 Передать в обмен или HTTP-сервис:
Base64Строка(ДвоичныеДанные)даёт текст, обратно —Base64Значение(Строка).
Размер базы и очистка хранилища
Файлы — главная причина, по которой база разрастается с 2 ГБ до 40 ГБ. Перед чисткой найдите, где лежит объём.
Как найти тяжёлые записи
Обработка на сервере обходит записи и считает размер после распаковки:
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Т.Ссылка КАК Ссылка,
| Т.Файл КАК Файл
|ИЗ
| Справочник.Договоры КАК Т");
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ДвоичныеДанные = Выборка.Файл.Получить();
Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные")
И ДвоичныеДанные.Размер() > 10 * 1024 * 1024 Тогда
Сообщить(СтрШаблон("%1: %2 МБ", Выборка.Ссылка,
Формат(ДвоичныеДанные.Размер() / 1048576, "ЧДЦ=1")));
КонецЕсли;
КонецЦикла;
На большой таблице запускайте её вне рабочего времени: каждый файл распаковывается в память сервера. Если вы заранее храните РазмерФайла в отдельном реквизите, ту же выборку можно сделать обычным запросом с условием по размеру — без распаковки.
Как очистить реквизит
Пустое хранилище записывается так:
ОбъектДанных.Файл = Новый ХранилищеЗначения(Неопределено);
ОбъектДанных.РазмерФайла = 0;
ОбъектДанных.Записать();
Для регистра сведений проще удалить саму запись через набор записей с отбором по владельцу.
Перед массовой очисткой сделайте резервную копию базы — удалённые файлы назад не вернуть.
Почему база не уменьшилась
После очистки место освобождается внутри файлов базы, но сами файлы на диске не сжимаются.
| Вариант базы | Как вернуть место |
|---|---|
| Файловая (1Cv8.1CD) | Конфигуратор → Администрирование → Тестирование и исправление с галочкой сжатия таблиц информационной базы |
| MS SQL Server | Сжатие базы или файла данных средствами SQL Server |
| PostgreSQL | VACUUM FULL по таблице или базе |
Тестирование и исправление файловой базы требует монопольного доступа: пользователи должны выйти. VACUUM FULL блокирует обрабатываемую таблицу, поэтому его тоже запускают в окно обслуживания.
Ошибки при записи и чтении файла
ТекущийОбъект в ПередЗаписьюНаСервере, а не в Объект формы, и что адрес временного хранилища не пустой.УникальныйИдентификатор формы при помещении файла и не удаляйте адрес до записи объекта.ДвоичныеДанные напрямую. Их нужно обернуть: Новый ХранилищеЗначения(ДвоичныеДанные).ГДЕ, УПОРЯДОЧИТЬ ПО и СГРУППИРОВАТЬ ПО. Фильтруйте по служебным реквизитам: имени, размеру, дате.Если код написан верно, а файл после записи всё равно пустой, загляните в подписки на события и модуль объекта. Иногда ПередЗаписью перезаполняет реквизиты, и ваше значение затирается.
Частые вопросы
Можно ли хранить в ХранилищеЗначения не файл, а структуру или таблицу?
Структура, ТаблицаЗначений, Массив, Картинка. Объекты, ссылающиеся на внешние ресурсы (например, COMОбъект), не сериализуются.Какой максимальный размер файла можно записать?
Сохраняется ли имя файла внутри хранилища?
Попадает ли файл из хранилища в резервную копию .dt?
Можно ли записать файл из толстого клиента без временного хранилища?
ХранилищеЗначения доступен, поэтому присвоить его объекту можно прямо там. Но такой код не заработает в тонком и веб-клиенте, поэтому для универсальной формы используйте временное хранилище.