Файл в реквизите типа ХранилищеЗначения: запись, чтение и очистка

· 9 мин

Содержание

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

Тема общая для платформы 1С:Предприятие 8.3 и не зависит от конфигурации: код одинаково работает в БП, ЗУП, УТ и в собственной разработке. Примеры даны для тонкого клиента и асинхронных методов платформы 8.3.18 и новее.

Как 1С хранит файл в реквизите ХранилищеЗначения

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

Что важно знать до написания кода:

  • 📦 Внутри лежат только байты. Имя файла, расширение, дата и размер в хранилище не сохраняются. Для них заводят отдельные реквизиты рядом: ИмяФайла, Расширение, РазмерФайла.
  • 🖥️ Тип живёт на сервере. В тонком клиенте и веб-клиенте ХранилищеЗначения недоступно. Реквизит этого типа не передаётся в форму, поэтому Объект.Файл на клиенте не прочитать и не заполнить.
  • 🗜️ Сжатие — по желанию. Второй параметр конструктора, Новый СжатиеДанных(N), где N от 0 до 9. Без него данные пишутся как есть.
  • 🔍 В запросах поле ограничено. Его можно выбрать, но нельзя использовать в условиях, сортировке и группировке.

Где держать файлы: в объекте или отдельно

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

ВариантКогда подходитМинусы
Реквизит в самом объекте1 небольшой файл: логотип, скан подписи, шаблон до 1 МБОбъект тяжелеет, растёт объём при каждом чтении
Отдельный регистр сведений (Владелец, ИмяФайла, Данные)Много вложений к документу, файлы от 1 МБНужен отдельный код записи и чтения
Файлы на диске, в базе только путьСотни мегабайт и гигабайтыРезервное копирование базы не покрывает файлы

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

Порядок записи файла в хранилище значений

Вся цепочка от выбора файла пользователем до строки в базе выглядит так:

  1. На клиенте пользователь выбирает файл. Платформа отправляет его во временное хранилище и возвращает адрес — строку вида e1cib/tempstorage/....
  2. Адрес сохраняют в реквизит формы (строку). Сам файл ещё не в базе.
  3. При записи объекта сервер достаёт данные: ПолучитьИзВременногоХранилища(Адрес) возвращает ДвоичныеДанные.
  4. Из двоичных данных создают Новый ХранилищеЗначения(...) и присваивают реквизиту объекта.
  5. Объект записывается. Временное хранилище освобождают после успешной записи.

Передавайте в метод помещения файла УникальныйИдентификатор формы. Тогда данные живут во временном хранилище, пока открыта форма. Без идентификатора они удаляются после завершения текущего серверного вызова, и на шаге 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
PostgreSQLVACUUM FULL по таблице или базе

Тестирование и исправление файловой базы требует монопольного доступа: пользователи должны выйти. VACUUM FULL блокирует обрабатываемую таблицу, поэтому его тоже запускают в окно обслуживания.

Ошибки при записи и чтении файла

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

Если код написан верно, а файл после записи всё равно пустой, загляните в подписки на события и модуль объекта. Иногда ПередЗаписью перезаполняет реквизиты, и ваше значение затирается.

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

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