Файл в ХранилищеЗначения 1С: реквизит, код записи и чтение обратно
Содержание
Файл помещается в хранилище значений так: читаете его в ДвоичныеДанные, оборачиваете в Новый ХранилищеЗначения(...) и присваиваете результат реквизиту типа ХранилищеЗначения. Записали объект, и файл лежит прямо в базе. Обратно данные достаются методом Получить().
Весь фокус в деталях: где взять реквизит, как передать файл с клиента на сервер, как не раздуть базу и что делать, если вместо файла приходит Неопределено. Ниже всё по порядку, с кодом, который можно копировать.
Хранилище значений подходит для небольших файлов: подписи, миниатюры, служебные вложения. Сканы договоров по 20 МБ и архивы туда класть не надо, для них есть присоединённые файлы.
Что проверить перед стартом
Сначала убедитесь, что хранилище значений вам вообще нужно. Сравните три способа держать файл в базе:
| Способ | Когда подходит | Риски |
|---|---|---|
Реквизит ХранилищеЗначения | Один небольшой файл «в нагрузку» к объекту: картинка, подпись, шаблон | Нет имени, даты и версий файла, нет штатного просмотра, раздувает таблицу объекта |
| Присоединённые файлы (регистр сведений и справочник файлов) | Пользователи прикладывают документы, нужны имена, даты, история | Нужна подсистема работы с файлами в конфигурации |
| Файл на диске, в базе только путь | Крупные файлы, видео, архивы | Путь ломается при переносе базы, нужны права на папку |
Если файл маленький и привязан к одному объекту, хранилище значений удобно. Иначе выбирайте второй или третий вариант.
Права и режим работы
Чтобы записать данные, нужны обычные права на изменение объекта, в котором лежит реквизит. Отдельного права «на хранилище» нет.
Ещё два момента:
- ✅ Файл читает тот, кто запускает код. На клиент-серверной базе сервер 1С работает от своей учётной записи, и к сетевой папке у неё может не быть доступа.
- ✅ Путь вида
C:\Temp\file.pdfна сервере указывает на диск сервера, а не вашего компьютера. С клиента файл надо передавать через временное хранилище.
Как завести реквизит под файл в Конфигураторе
Конфигуратор нужен для одного: создать место, куда файл будет попадать. Сам файл через него не загружается, это делается кодом или из формы.
- Откройте конфигурацию и найдите объект (справочник, документ, регистр сведений ), в который нужно хранить файл.
- В ветке объекта добавьте новый реквизит. Имя дайте по смыслу, например
ВложениеилиПодписьФайл. - В палитре свойств выберите тип
ХранилищеЗначения. Он находится в списке типов среди прочих. - Перетащите реквизит в нужную форму или добавьте его в элементы формы, если хотите, чтобы пользователь с ним работал.
- Сохраните конфигурацию и обновите конфигурацию базы данных.
Для отладки хватит и константы типа ХранилищеЗначения. Если реквизит нужен «на один раз», не плодите их в справочнике: заведите отдельный регистр сведений с одним ресурсом этого типа.
В режиме 1С:Предприятие поле такого реквизита на форме само по себе ничего не покажет: платформа не знает, что внутри. Поэтому кнопки «Загрузить» и «Сохранить на диск» придётся сделать самим.
Запись файла встроенным языком
Самый короткий вариант, когда файл лежит там, где его видит процесс 1С (файловая база, серверный код с доступной папкой):
ДвоичныеДанные = Новый ДвоичныеДанные("C:\Обмен\логотип.png");
Объект = Справочники.Организации.НайтиПоКоду("000000001").ПолучитьОбъект();
Объект.Вложение = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
Объект.Записать();
Что здесь происходит:
Новый ДвоичныеДанные(путь)читает файл целиком в память.Новый СжатиеДанных(9)задаёт уровень сжатия от 0 до 9. Для текстов, таблиц и документов сжатие даёт большой выигрыш. Для JPEG, ZIP и PDF почти ничего не даст, зато потратит процессор.Объект.Записать()кладёт данные в базу. До записи в базе ничего не появится.
Если сжатие не нужно, второй параметр опускают:
Объект.Вложение = Новый ХранилищеЗначения(ДвоичныеДанные);
Передача с клиента на сервер
В управляемом приложении пользователь выбирает файл на своём компьютере. Схема такая: клиент загружает файл во временное хранилище, а серверная процедура забирает его оттуда по адресу.
Клиентская часть:
&НаКлиенте
Процедура ЗагрузитьФайл(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеЗагрузки", ЭтотОбъект);
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
НачатьПомещениеФайлов(Оповещение, , Диалог, Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗагрузки(ПомещенныеФайлы, ДопПараметры) Экспорт
Если ПомещенныеФайлы = Неопределено Тогда
Возврат;
КонецЕсли;
СохранитьВХранилище(ПомещенныеФайлы[0].Хранение);
КонецПроцедуры
Серверная часть:
&НаСервере
Процедура СохранитьВХранилище(Адрес)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(Адрес);
ОбъектФормы = РеквизитФормыВЗначение("Объект");
ОбъектФормы.Вложение = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
ЗначениеВРеквизитФормы(ОбъектФормы, "Объект");
КонецПроцедуры
Затем пользователь жмёт «Записать» на форме, и данные уходят в базу.
Если форма открыта у пользователя, а вы пишете в реквизит напрямую из другого места, он может перезаписать ваше значение старым при сохранении формы. Правьте данные через форму или блокируйте объект.
Что делать, если имя файла тоже нужно
Хранилище значений хранит только содержимое. Имя и расширение потеряются, и потом вы не поймёте, PDF это или DOCX. Поэтому рядом заведите строковый реквизит ИмяФайла и пишите туда ПомещенныеФайлы[0].Имя (на клиенте) или имя из пути.
Можно сложить всё в структуру:
Данные = Новый Структура("Имя, Содержимое", "договор.pdf", ДвоичныеДанные);
Объект.Вложение = Новый ХранилищеЗначения(Данные);
Так имя и данные едут вместе. Минус: в запросе их уже не прочесть, только кодом.
Загрузка пачки файлов из папки
Когда нужно разово залить много файлов (например, картинки номенклатуры), удобнее обработка, которая идёт по папке циклом:
Файлы = НайтиФайлы("C:\Обмен\Картинки", "*.jpg");
Для Каждого Файл Из Файлы Цикл
Элемент = Справочники.Номенклатура.НайтиПоАртикулу(Файл.ИмяБезРасширения);
Если Элемент.Пустая() Тогда
Продолжить;
КонецЕсли;
Объект = Элемент.ПолучитьОбъект();
Объект.Картинка = Новый ХранилищеЗначения(Новый ДвоичныеДанные(Файл.ПолноеИмя));
Объект.Записать();
КонецЦикла;
Три правила для такого цикла:
- 🔹 Запись каждого объекта в своей транзакции и без общей блокировки на всю партию, иначе другие пользователи будут ждать.
- 🔹 Ведите счётчик и пишите в журнал, какие файлы не нашли пару. Через неделю вы сами не вспомните, что пропустили.
- 🔹 Прогоните сначала на копии базы. Одна ошибка в сопоставлении имён перепишет картинки не тем элементам, и откатить это будет нечем.
Запускайте такую загрузку вне рабочих часов: на 5 000 файлов по 300 КБ сервер упрётся в диск и память, а пользователи заметят задержки.
Как организовать хранение, чтобы потом не мучиться
Хранилище значений не имеет папок и имён. Структуру придумываете вы, и от неё зависит, найдёте ли вы нужный файл через год.
Практичные правила:
- 📌 Один реквизит хранит один файл. Не складывайте в него массивы из десятка вложений: достать один файл придётся целиком вместе с остальными.
- 📌 Имя, тип, дату загрузки и автора храните в отдельных реквизитах рядом. Только по ним можно отбирать данные запросом.
- 📌 Для нескольких файлов у одного объекта делайте табличную часть: колонка
ФайлтипаХранилищеЗначения, колонкиИмяиРасширениерядом. - 📌 Не кладите хранилище в реквизит справочника, который выбирают по сотням раз в минуту. Каждый запрос к элементу тянет и тяжёлое поле, если вы его не исключили.
Ещё один приём: выносите тяжёлое в отдельный регистр сведений с измерением «Ссылка на владельца». Основной справочник остаётся лёгким, а файл читается только тогда, когда он нужен.
Как достать файл обратно и пользоваться им
Чтение зеркально записи. Метод Получить() возвращает то, что вы клали: ДвоичныеДанные или структуру.
ДвоичныеДанные = Объект.Вложение.Получить();
Если ДвоичныеДанные = Неопределено Тогда
Возврат; // файл не загружали
КонецЕсли;
ДвоичныеДанные.Записать("C:\Выгрузка\логотип.png");
Показать файл или скачать его пользователю
Для клиента нужен путь через временное хранилище:
&НаСервере
Функция АдресФайла()
ДвоичныеДанные = Объект.Вложение.Получить();
Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура Скачать(Команда)
ПолучитьФайл(АдресФайла(), "логотип.png", Истина);
КонецПроцедуры
Для картинки на форме достаточно положить адрес в реквизит формы типа Картинка или в поле с отображением картинки. Файл не нужно сохранять на диск.
Прочитать данные запросом
В тексте запроса поле хранилища доступно, но выбирать его лучше точечно:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Вложение ИЗ Справочник.Организации ГДЕ Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ОрганизацияСсылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Данные = Выборка.Вложение.Получить();
КонецЕсли;
Отбор или сортировка по содержимому хранилища невозможны. Условия накладывайте по соседним реквизитам.
Проверка результата после записи
Не поверяйте на слово, что файл «ушёл». Прочитайте его обратно и сравните размер:
Исходный = Новый ДвоичныеДанные("C:\Обмен\логотип.png");
Сохранённый = Объект.Вложение.Получить();
Сообщить(Исходный.Размер() = Сохранённый.Размер());
Если размеры совпали, данные целы. Сжатие на размер результата Получить() не влияет: платформа возвращает исходное содержимое, распакованное целиком. Расхождение значит, что в реквизит попало что-то другое, например строка или структура.
Ошибки при записи и чтении, о которых нигде не пишут
Разберём симптомы, которые встречаются чаще всего, и причины, которых не видно из текста ошибки.
Если база начала разрастаться
Хранилища значений занимают место в самой таблице объекта. Через пару лет вложений по 500 КБ на 20 000 записей это уже 10 ГБ, и база становится неповоротливой: дольше копируется, дольше работает выгрузка в .dt.
Что делать:
- Узнайте, какие объекты весят больше всего. В SQL-базе это видно по размеру таблиц, в файловой базе по размеру файла
1Cv8.1CDдо и после чистки. - Для старых записей очистите хранилище:
Объект.Вложение = Новый ХранилищеЗначения(Неопределено); - Запустите тестирование и исправление базы с реструктуризацией или выгрузите и загрузите базу, чтобы вернуть освобождённое место.
Свободное место внутри файла 1Cv8.1CD само не возвращается. Размер уменьшится только после выгрузки и загрузки информационной базы.
Когда хранилище значений использовать не стоит
Есть случаи, когда лучше сразу выбрать другой путь:
- 🚫 Файлы пользователей, которые нужно искать, версионировать и показывать с датой. Для этого есть присоединённые файлы.
- 🚫 Файлы больше нескольких десятков мегабайт. Нагрузка на память сервера и размер базы того не стоят.
- 🚫 Данные, которые нужно читать другой системой напрямую из SQL. Содержимое хранилища упаковано в формат 1С, и без самой платформы его не распаковать.
- 🚫 Данные, которые часто меняются. Каждая запись переписывает всё поле целиком.
Хороший признак правильного применения: файл маленький, редко меняется, привязан к одному объекту и читается только когда открыли его карточку.
Частые вопросы
Какой максимальный размер файла можно положить в хранилище значений?
Можно ли хранить в нём не файл, а таблицу или структуру?
ДвоичныеДанные.Сохранится ли имя файла?
Можно ли очистить хранилище у реквизита?
Новый ХранилищеЗначения(Неопределено) и запишите объект. Метод Получить() после этого вернёт Неопределено.