ХранилищеЗначений в коде 1С: файлы, картинки и таблицы внутри реквизита

· 9 мин

Содержание

Чтобы записать данные в хранилище значений 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);
Константы.НастройкиИнтеграции.Установить(Новый ХранилищеЗначений(Настройки));

// Чтение: первый Получить() — у константы, второй — у хранилища
СохранённыеНастройки = Константы.НастройкиИнтеграции.Получить().Получить();

В регистре сведений тип ХранилищеЗначений допустим для ресурсов и реквизитов, но не для измерений: по измерениям строится ключ записи, а хранилище сравнивать нельзя. Запись идёт через менеджер записи или набор записей:

МенеджерЗаписи = РегистрыСведений.ФотоТоваров.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Номенклатура = Товар;
МенеджерЗаписи.Фото = Новый ХранилищеЗначений(ДанныеКартинки);
МенеджерЗаписи.Записать();

Хранилище значений в управляемой форме

Главное правило: реквизит типа ХранилищеЗначений не доходит до тонкого клиента . Поле формы к нему напрямую не привязать. Поэтому работают через ТекущийОбъект в серверных обработчиках формы и через временное хранилище.

Рабочая схема для формы элемента с картинкой:

  1. Добавьте реквизит формы АдресКартинки типа Строка и поле картинки, привязанное к нему.
  2. В ПриЧтенииНаСервере достаньте данные и положите во временное хранилище.
  3. Когда пользователь выбирает новую картинку, сохраните её адрес в тот же реквизит формы.
  4. В ПередЗаписьюНаСервере заберите данные по адресу и упакуйте в хранилище.
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)

	ДанныеКартинки = ТекущийОбъект.Фото.Получить();
	Если ДанныеКартинки <> Неопределено Тогда
		АдресКартинки = ПоместитьВоВременноеХранилище(ДанныеКартинки, УникальныйИдентификатор);
	КонецЕсли;

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

&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)

	Если ЭтоАдресВременногоХранилища(АдресКартинки) Тогда
		ДанныеКартинки = ПолучитьИзВременногоХранилища(АдресКартинки);
		ТекущийОбъект.Фото = Новый ХранилищеЗначений(ДанныеКартинки);
	КонецЕсли;

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

Передача УникальныйИдентификатор формы во второй параметр привязывает временные данные к форме: платформа удалит их, когда форму закроют.

Для показа уже записанной картинки можно обойтись без временного хранилища. Функция ПолучитьНавигационнуюСсылку(Объект.Ссылка, "Фото") возвращает ссылку на реквизит, и поле картинки отображает её напрямую. Для нового, ещё не записанного объекта этот приём не работает: ссылки пока нет.

Какие типы данных можно положить в ХранилищеЗначений

В хранилище помещается значение, которое платформа умеет сериализовать. На практике это почти всё, с чем работают в прикладном коде.

✅ Сохраняются без проблем:

  • примитивы: Строка, Число, Дата, Булево;
  • ДвоичныеДанные — универсальный путь для любых файлов;
  • Картинка, ТабличныйДокумент, ТекстовыйДокумент;
  • коллекции: Массив, Структура, Соответствие, СписокЗначений, ТаблицаЗначений, ДеревоЗначений;
  • ссылки на объекты базы: элементы справочников, документы, перечисления.

❌ Не сохраняются или теряют смысл:

  • COMОбъект и внешние объекты — живут только в текущем процессе;
  • открытые потоки и средства чтения/записи: ЧтениеXML, ЗаписьXML, ЧтениеТекста;
  • объекты форм и элементов формы.

Две особенности, о которых узнают уже после ошибки.

Ссылки хранятся как ссылки. Если положить в хранилище таблицу с колонкой Контрагент, внутри окажется ссылка, а не наименование. Удалили контрагента — при чтении получите битую ссылку. Нужен снимок данных на дату — сохраняйте строки с наименованием и ИНН, а не одну ссылку.

Вложенные коллекции работают. Структура внутри массива внутри структуры спокойно переживает запись и чтение. Это удобно для настроек: одна константа вместо десятка.

Пример с таблицей значений:

Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Номенклатура");
Таблица.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));

Строка = Таблица.Добавить();
Строка.Номенклатура = Товар;
Строка.Количество = 5;

ДокументОбъект.СнимокТабличнойЧасти = Новый ХранилищеЗначений(Таблица, Новый СжатиеДанных(9));

// Позже
ВосстановленнаяТаблица = ДокументОбъект.СнимокТабличнойЧасти.Получить();
ВсегоКоличество = ВосстановленнаяТаблица.Итог("Количество");

Итог() работает, потому что колонке Количество задан числовой тип. Без описания типов метод вернёт ноль или упадёт на нечисловых значениях.

Память, размер базы и очистка хранилища

Хранилище само по себе не расходует память, пока его не прочитали. Проблемы начинаются от привычек кода.

Что раздувает потребление памяти

  • 🐢 ПолучитьОбъект() в цикле по сотне элементов, у каждого из которых файл в несколько мегабайт. Объект читается целиком, вместе с хранилищем.
  • 🐢 Обращение через точку Ссылка.ФайлДоговора тоже читает объект целиком. Для одного элемента терпимо, для списка — медленно.
  • 🐢 Запрос ВЫБРАТЬ * по таблице с хранилищем тянет все файлы, даже если нужны только наименования.
  • 🐢 Временное хранилище без идентификатора формы: данные висят до конца сеанса.

Решение простое: в запросах перечисляйте только нужные поля, а большие файлы выносите в отдельный справочник или регистр сведений . Основной объект тогда хранит ссылку на «файловую» запись и читается быстро.

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

Пустое хранилище создают тем же конструктором:

ДоговорОбъект = СсылкаДоговор.ПолучитьОбъект();
ДоговорОбъект.ФайлДоговора = Новый ХранилищеЗначений(Неопределено);
ДоговорОбъект.ИмяФайла = "";
ДоговорОбъект.Записать();

После записи Получить() вернёт Неопределено, а данные из строки таблицы базы уйдут.

Для временного хранилища — УдалитьИзВременногоХранилища(Адрес). Для переменных в длинной процедуре — присвоить им Неопределено, когда данные больше не нужны: так большой массив байтов не доживёт до конца обработки.

Массовая очистка

Удалить файлы у тысяч объектов одной обработкой — тяжёлая операция. Каждая запись объекта запускает его обработчики, регистрирует изменения для обменов и пишет журнал регистрации.

Перед массовой очисткой сделайте резервную копию базы. Файлы, удалённые из хранилищ, вернуть можно только из копии.

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

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

Типичные ошибки при работе с ХранилищемЗначений

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

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

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

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