ХранилищеЗначений в коде 1С: файлы, картинки и таблицы внутри реквизита
Содержание
Чтобы записать данные в хранилище значений 1С, оберните их конструктором: Объект.Файл = Новый ХранилищеЗначений(Данные, Новый СжатиеДанных(9));, затем запишите объект методом Записать(). Читают обратно одним вызовом: Данные = Объект.Файл.Получить();. Вся работа идёт на сервере, на тонкий клиент значение хранилища не передаётся.
Вариантов хранения в 1С несколько, и путают их часто. Реквизит типа ХранилищеЗначений держит данные в самой базе, навсегда, рядом с объектом. Временное хранилище живёт до конца сеанса или до закрытия формы, оно нужно для передачи данных между клиентом и сервером. Хранилища настроек (ХранилищеОбщихНастроек и другие) сохраняют пользовательские параметры. Ниже речь о первом варианте и о том, как он стыкуется со вторым.
Что хранит ХранилищеЗначений и когда его выбирать
ХранилищеЗначений — это обёртка. Она упаковывает почти любое значение 1С в двоичный вид и позволяет положить его в реквизит справочника, документа, регистра сведений или константы. В таблице СУБД такое поле лежит как двоичные данные, поэтому внутрь платформа не заглядывает.
Типичные задачи, под которые выбирают этот тип:
- 📎 файлы: договор в PDF, скан паспорта, выгрузка XML от контрагента;
- 🖼️ картинки: фото товара, подпись и печать для печатной формы, логотип;
- 📊 структуры данных: настройки обработки, таблица значений с расчётом, дерево значений;
- 🧾 готовые формы: сохранённый
ТабличныйДокументпечатной формы на момент проведения; - ⚙️ служебные данные: последний ответ веб-сервиса, состояние обмена.
Где тип не подходит:
| Задача | Почему не хранилище | Что взять |
|---|---|---|
| Отбор и поиск по значению | Поле нельзя использовать в условиях, сортировке и группировке запроса | Обычные реквизиты типа Строка, Число, Дата |
| Длинный текст для поиска | Внутрь хранилища запрос не смотрит | Строка неограниченной длины |
| Передача данных в форму и обратно | На тонкий клиент хранилище не уходит | Временное хранилище |
| Сотни мегабайт файлов на объект | Раздувается база и резервные копии | Хранение файлов в томах на диске |
Если нужно и хранить файл, и искать документы по его признакам, заведите рядом обычные реквизиты: имя файла, расширение, размер, дату загрузки. Ищите по ним, а содержимое берите из хранилища только для найденной строки.
Синтаксис: конструктор, сжатие и метод Получить()
У объекта ровно два действия: создать и получить.
Создание. Конструктор принимает значение и, по желанию, параметры сжатия:
Хранилище = Новый ХранилищеЗначений(Значение);
Хранилище = Новый ХранилищеЗначений(Значение, Новый СжатиеДанных(9));
Уровень СжатиеДанных задают числом от 0 до 9. Ноль — без сжатия, девять — максимальное. Без второго параметра данные сохраняются как есть.
Чтение. Метод Получить() возвращает исходное значение того же типа, что положили:
Значение = Хранилище.Получить();
Если в хранилище ничего не клали, Получить() вернёт Неопределено. Эту проверку и используют, чтобы понять, заполнен ли реквизит.
Сжатие стоит включать не всегда. Ориентир по типу данных:
| Данные | Эффект от сжатия | Рекомендация |
|---|---|---|
| XML, JSON, текст, таблицы значений | Сильный, размер падает в разы | Уровень 9 |
ТабличныйДокумент, BMP | Заметный | Уровень 6–9 |
| JPEG, PNG, PDF со сканами | Почти нулевой, форматы уже сжаты | Без сжатия |
| ZIP, RAR, 7z | Нулевой | Без сжатия |
Сжатие тратит процессор сервера при записи и чтении. Для уже сжатых форматов это работа впустую.
Как записать данные в хранилище значений: пример на справочнике
Возьмём справочник Договоры с реквизитом ФайлДоговора типа ХранилищеЗначений и строковыми реквизитами ИмяФайла и Расширение. Задача: положить файл с диска в договор и потом достать его.
Запись файла в реквизит объекта
Код выполняется на сервере. Файл уже должен лежать на сервере или прийти в виде двоичных данных:
&НаСервереБезКонтекста
Процедура СохранитьФайлВДоговор(СсылкаДоговор, АдресФайла, ИмяФайла)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайла);
ДоговорОбъект = СсылкаДоговор.ПолучитьОбъект();
ДоговорОбъект.ФайлДоговора = Новый ХранилищеЗначений(ДвоичныеДанные);
ДоговорОбъект.ИмяФайла = ИмяФайла;
ДоговорОбъект.Записать();
УдалитьИзВременногоХранилища(АдресФайла);
КонецПроцедуры
Сжатие здесь не включено намеренно: договоры обычно приходят в PDF.
На клиенте файл попадает во временное хранилище через диалог выбора. В актуальных версиях платформы для этого есть НачатьПомещениеФайлаНаСервер, в более старых коде встречается НачатьПомещениеФайла. Обе процедуры асинхронные и возвращают адрес во временном хранилище, который вы передаёте на сервер.
Чтение через запрос
Если нужен один реквизит, не открывайте объект целиком. Запрос вытащит только нужное поле:
&НаСервереБезКонтекста
Функция ПолучитьФайлДоговора(СсылкаДоговор)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Договоры.ФайлДоговора КАК ФайлДоговора,
| Договоры.ИмяФайла КАК ИмяФайла
|ИЗ
| Справочник.Договоры КАК Договоры
|ГДЕ
| Договоры.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаДоговор);
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
Возврат Неопределено;
КонецЕсли;
Данные = Выборка.ФайлДоговора.Получить();
Если Данные = Неопределено Тогда
Возврат Неопределено;
КонецЕсли;
Возврат Новый Структура("Данные, ИмяФайла", Данные, Выборка.ИмяФайла);
КонецФункции
Выбрать поле хранилища в запросе можно, а вот сравнивать его, сортировать и группировать по нему нельзя. Отбор всегда делайте по другим полям, как здесь по Ссылка.
Запись в константу и регистр сведений
Константа типа ХранилищеЗначений удобна для настроек, общих на всю базу:
Настройки = Новый Структура;
Настройки.Вставить("АдресСервиса", "https://example.local/api");
Настройки.Вставить("Таймаут", 30);
Константы.НастройкиИнтеграции.Установить(Новый ХранилищеЗначений(Настройки));
// Чтение: первый Получить() — у константы, второй — у хранилища
СохранённыеНастройки = Константы.НастройкиИнтеграции.Получить().Получить();
В регистре сведений тип ХранилищеЗначений допустим для ресурсов и реквизитов, но не для измерений: по измерениям строится ключ записи, а хранилище сравнивать нельзя. Запись идёт через менеджер записи или набор записей:
МенеджерЗаписи = РегистрыСведений.ФотоТоваров.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Номенклатура = Товар;
МенеджерЗаписи.Фото = Новый ХранилищеЗначений(ДанныеКартинки);
МенеджерЗаписи.Записать();
Хранилище значений в управляемой форме
Главное правило: реквизит типа ХранилищеЗначений не доходит до тонкого клиента
. Поле формы к нему напрямую не привязать. Поэтому работают через ТекущийОбъект в серверных обработчиках формы и через временное хранилище.
Рабочая схема для формы элемента с картинкой:
- Добавьте реквизит формы
АдресКартинкитипа Строка и поле картинки, привязанное к нему. - В
ПриЧтенииНаСервередостаньте данные и положите во временное хранилище. - Когда пользователь выбирает новую картинку, сохраните её адрес в тот же реквизит формы.
- В
ПередЗаписьюНаСерверезаберите данные по адресу и упакуйте в хранилище.
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
ДанныеКартинки = ТекущийОбъект.Фото.Получить();
Если ДанныеКартинки <> Неопределено Тогда
АдресКартинки = ПоместитьВоВременноеХранилище(ДанныеКартинки, УникальныйИдентификатор);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресКартинки) Тогда
ДанныеКартинки = ПолучитьИзВременногоХранилища(АдресКартинки);
ТекущийОбъект.Фото = Новый ХранилищеЗначений(ДанныеКартинки);
КонецЕсли;
КонецПроцедуры
Передача УникальныйИдентификатор формы во второй параметр привязывает временные данные к форме: платформа удалит их, когда форму закроют.
Для показа уже записанной картинки можно обойтись без временного хранилища. Функция ПолучитьНавигационнуюСсылку(Объект.Ссылка, "Фото") возвращает ссылку на реквизит, и поле картинки отображает её напрямую. Для нового, ещё не записанного объекта этот приём не работает: ссылки пока нет.
Какие типы данных можно положить в ХранилищеЗначений
В хранилище помещается значение, которое платформа умеет сериализовать. На практике это почти всё, с чем работают в прикладном коде.
✅ Сохраняются без проблем:
- примитивы: Строка, Число, Дата, Булево;
ДвоичныеДанные— универсальный путь для любых файлов;Картинка,ТабличныйДокумент,ТекстовыйДокумент;- коллекции:
Массив,Структура,Соответствие,СписокЗначений,ТаблицаЗначений,ДеревоЗначений; - ссылки на объекты базы: элементы справочников, документы, перечисления.
❌ Не сохраняются или теряют смысл:
COMОбъекти внешние объекты — живут только в текущем процессе;- открытые потоки и средства чтения/записи:
ЧтениеXML,ЗаписьXML,ЧтениеТекста; - объекты форм и элементов формы.
Две особенности, о которых узнают уже после ошибки.
Ссылки хранятся как ссылки. Если положить в хранилище таблицу с колонкой Контрагент, внутри окажется ссылка, а не наименование. Удалили контрагента — при чтении получите битую ссылку. Нужен снимок данных на дату — сохраняйте строки с наименованием и ИНН, а не одну ссылку.
Вложенные коллекции работают. Структура внутри массива внутри структуры спокойно переживает запись и чтение. Это удобно для настроек: одна константа вместо десятка.
Пример с таблицей значений:
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Номенклатура");
Таблица.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
Строка = Таблица.Добавить();
Строка.Номенклатура = Товар;
Строка.Количество = 5;
ДокументОбъект.СнимокТабличнойЧасти = Новый ХранилищеЗначений(Таблица, Новый СжатиеДанных(9));
// Позже
ВосстановленнаяТаблица = ДокументОбъект.СнимокТабличнойЧасти.Получить();
ВсегоКоличество = ВосстановленнаяТаблица.Итог("Количество");
Итог() работает, потому что колонке Количество задан числовой тип. Без описания типов метод вернёт ноль или упадёт на нечисловых значениях.
Память, размер базы и очистка хранилища
Хранилище само по себе не расходует память, пока его не прочитали. Проблемы начинаются от привычек кода.
Что раздувает потребление памяти
- 🐢
ПолучитьОбъект()в цикле по сотне элементов, у каждого из которых файл в несколько мегабайт. Объект читается целиком, вместе с хранилищем. - 🐢 Обращение через точку
Ссылка.ФайлДоговоратоже читает объект целиком. Для одного элемента терпимо, для списка — медленно. - 🐢 Запрос
ВЫБРАТЬ *по таблице с хранилищем тянет все файлы, даже если нужны только наименования. - 🐢 Временное хранилище без идентификатора формы: данные висят до конца сеанса.
Решение простое: в запросах перечисляйте только нужные поля, а большие файлы выносите в отдельный справочник или регистр сведений . Основной объект тогда хранит ссылку на «файловую» запись и читается быстро.
Как очистить хранилище значений
Пустое хранилище создают тем же конструктором:
ДоговорОбъект = СсылкаДоговор.ПолучитьОбъект();
ДоговорОбъект.ФайлДоговора = Новый ХранилищеЗначений(Неопределено);
ДоговорОбъект.ИмяФайла = "";
ДоговорОбъект.Записать();
После записи Получить() вернёт Неопределено, а данные из строки таблицы базы уйдут.
Для временного хранилища — УдалитьИзВременногоХранилища(Адрес). Для переменных в длинной процедуре — присвоить им Неопределено, когда данные больше не нужны: так большой массив байтов не доживёт до конца обработки.
Массовая очистка
Удалить файлы у тысяч объектов одной обработкой — тяжёлая операция. Каждая запись объекта запускает его обработчики, регистрирует изменения для обменов и пишет журнал регистрации.
Перед массовой очисткой сделайте резервную копию базы. Файлы, удалённые из хранилищ, вернуть можно только из копии.
Запускайте такую обработку вне рабочего времени и порциями по несколько сотен объектов с отдельной транзакцией на порцию. Если в базе настроен обмен, учтите, что изменённые объекты уйдут в узлы обмена.
Освободившееся место файл базы сам не отдаёт. Для файловой базы размер уменьшится после тестирования и исправления со сжатием таблиц в Конфигураторе. Этот режим требует монопольного доступа: все пользователи должны выйти. Для клиент-серверной базы место возвращают средствами СУБД, это задача администратора сервера.
Типичные ошибки при работе с ХранилищемЗначений
Ещё одна ловушка — тип реквизита. Если реквизит в конфигурации составного типа или типа ДвоичныеДанные, код с Новый ХранилищеЗначений(...) поведёт себя иначе, чем ожидаете. Откройте свойства реквизита в Конфигураторе и проверьте, что в поле «Тип» указано именно ХранилищеЗначений.