Запись данных в ХранилищеЗначений: от реквизита до файла

· 9 мин

Содержание

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

Типичная ситуация: в справочник «Номенклатура» нужно положить фото товара, а в документ — скан договора или выгрузку таблицы для повторной обработки. Ниже — рабочий код для каждого шага, от добавления реквизита в конфигураторе до чтения данных обратно. Механизм общий для платформы 8.3 и не зависит от конфигурации: БП, ЗУП, УТ или своя разработка.

Что такое ХранилищеЗначений и где оно живёт

ХранилищеЗначений — это контейнер, который упаковывает почти любое значение 1С в двоичный вид и позволяет записать его в реквизит объекта базы. Внутри может лежать картинка, файл, ТаблицаЗначений, Структура, Массив, табличный документ.

Ключевые свойства типа:

  • 📦 Упаковка целиком. Значение сериализуется при создании хранилища. Менять его «внутри» нельзя: получили, изменили, упаковали заново.
  • 🖥️ Доступность. Тип есть на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте его нет.
  • 🔍 Без запросов по содержимому. По реквизиту-хранилищу нельзя отбирать, сортировать и группировать. Его можно только выбрать в запросе и прочитать.
  • 🗜️ Сжатие по желанию. Второй параметр конструктора задаёт уровень сжатия от 0 до 9.

Как добавить реквизит в конфигураторе

  1. Откройте объект метаданных (справочник, документ, регистр сведений ).
  2. На вкладке «Данные» добавьте реквизит, например Фото или ДанныеФайла.
  3. В поле «Тип» выберите ХранилищеЗначения.
  4. Обновите конфигурацию базы данных (F7).

Для типовой конфигурации реквизит лучше добавить в расширение, а не в основную конфигурацию: так он переживёт обновления без ручного сравнения-объединения. Сами расширения появились в платформе 8.3.6, добавление своих реквизитов к заимствованным объектам — в более поздних версиях 8.3.

Программная запись в хранилище на сервере

Базовая схема одна для любого типа данных: получить значение → упаковать → присвоить реквизиту → записать объект.

Пример 1. Файл с диска сервера в справочник

&НаСервере
Процедура СохранитьФайлВТовар(СсылкаТовар, ПутьКФайлу)

    ДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);

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

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

Путь здесь — путь на машине, где работает сервер 1С, а не на компьютере пользователя. Для клиент-серверной базы это частая причина ошибки «файл не найден».

Пример 2. Таблица значений в регистр сведений

&НаСервере
Процедура СохранитьСнимокОстатков(Ключ, ТаблицаОстатков)

    МенеджерЗаписи = РегистрыСведений.СнимкиДанных.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.Ключ = Ключ;
    МенеджерЗаписи.Дата = ТекущаяДатаСеанса();
    МенеджерЗаписи.Данные = Новый ХранилищеЗначений(ТаблицаОстатков, Новый СжатиеДанных(9));
    МенеджерЗаписи.Записать(Истина);

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

Параметр Истина в Записать() заменяет существующую запись с тем же ключом. Регистр СнимкиДанных с измерением Ключ и ресурсами Дата и Данные нужно создать самому — в типовых его нет.

Пример 3. Структура настроек

Настройки = Новый Структура;
Настройки.Вставить("Склад", СкладПоУмолчанию);
Настройки.Вставить("ПорогОстатка", 10);
Настройки.Вставить("ДатаНачала", НачалоМесяца(ТекущаяДатаСеанса()));

ОбъектНастроек.Параметры = Новый ХранилищеЗначений(Настройки);
ОбъектНастроек.Записать();

Для мелкой структуры сжатие не нужно: выигрыш в байтах, а время на упаковку всё равно тратится.

Сохранение с формы: файл от пользователя

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

  1. Клиент передаёт файл во временное хранилище и получает адрес — строку вида e1cib/tempstorage/….
  2. Адрес сохраняется в реквизите формы типа «Строка».
  3. При записи объекта сервер берёт данные по адресу и упаковывает в реквизит.

Код на клиенте: выбор и передача файла

&НаКлиенте
Процедура ЗагрузитьФото(Команда)

    Оповещение = Новый ОписаниеОповещения("ЗагрузитьФотоЗавершение", ЭтотОбъект);
    НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);

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

&НаКлиенте
Процедура ЗагрузитьФотоЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт

    Если Не Результат Тогда
        Возврат; // пользователь закрыл диалог
    КонецЕсли;

    АдресФото = Адрес;
    Модифицированность = Истина;

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

Четвёртый параметр Истина открывает диалог выбора файла. Пятый — УникальныйИдентификатор формы: данные проживут во временном хранилище, пока форма открыта. В новых версиях платформы есть и метод НачатьПомещениеФайлаНаСервер с более подробным описанием результата — логика та же.

Код на сервере: упаковка при записи

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

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

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

Обработчик ПередЗаписьюНаСервере получает именно ТекущийОбъект — объект базы, а не данные формы. Реквизит-хранилище в данных формы недоступен, писать его можно только так.

Показ картинки на форме

Добавьте на форму поле картинки, привязанное к строковому реквизиту АдресФото. При открытии существующего элемента заполните адрес навигационной ссылкой на реквизит:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

    Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
        АдресФото = ПолучитьНавигационнуюСсылку(Объект.Ссылка, "Фото");
    КонецЕсли;

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

Платформа сама достанет картинку из хранилища по этой ссылке. В обработчике ПередЗаписьюНаСервере проверка ЭтоАдресВременногоХранилища отсеет навигационную ссылку, и старое фото не перезапишется лишний раз.

Временное хранилище: когда оно вместо постоянного

Временное хранилище не пишет данные в таблицы базы. Это область памяти сеанса для передачи данных между клиентом и сервером и между вызовами сервера.

ЗадачаЧто использоватьСколько живут данные
Хранить файл у элемента справочникаРеквизит типа ХранилищеЗначенияПока не перезаписали или не удалили объект
Передать файл с клиента на серверПоместитьВоВременноеХранилище, НачатьПомещениеФайлаДо закрытия формы, если передан её идентификатор
Вернуть результат фонового заданияПоместитьВоВременноеХранилище в задании, чтение по адресу на клиентеДо закрытия формы-владельца
Отдать пользователю файл на скачиваниеПоместитьВоВременноеХранилище + ПолучитьФайл на клиентеКороткое время, до завершения передачи

Основные методы глобального контекста:

  • ПоместитьВоВременноеХранилище(Данные, Адрес) — кладёт значение и возвращает адрес. Вторым параметром передают УникальныйИдентификатор формы или уже существующий адрес.
  • ПолучитьИзВременногоХранилища(Адрес) — возвращает значение.
  • УдалитьИзВременногоХранилища(Адрес) — освобождает память, не дожидаясь закрытия формы.
  • ЭтоАдресВременногоХранилища(Адрес) — проверка, что строка действительно адрес.

Без идентификатора формы данные во временном хранилище живут недолго — до конца вызова сервера. Рассчитывать на них в следующем действии пользователя нельзя: адрес вернёт Неопределено.

Большие файлы во временном хранилище занимают память рабочего процесса сервера. Если форма загружает десятки мегабайт, удаляйте данные методом УдалитьИзВременногоХранилища сразу после записи.

Что можно и что нельзя упаковать

В хранилище кладут значения, которые платформа умеет сериализовать. Объекты, существующие только в памяти сеанса, упаковать не получится — будет ошибка выполнения при вызове конструктора.

Можно сохранитьНельзя сохранить
Строка, Число, Дата, БулевоЗапрос, ПостроительЗапроса
Ссылки на объекты базыCOMОбъект
ДвоичныеДанные, КартинкаФорма и её элементы
ТаблицаЗначений, ДеревоЗначенийЧтениеXML, ЗаписьXML, потоки
Массив, Структура, СоответствиеОбъекты внешних компонент
ТабличныйДокументОткрытые файлы и соединения

Если в Структуре есть хотя бы одно несериализуемое свойство, не упакуется вся структура. Ищите виновника по очереди: упакуйте каждое свойство отдельно в Попытка … Исключение.

Защита записи от ошибок

Попытка
    ТекущийОбъект.ДанныеФайла = Новый ХранилищеЗначений(Данные, Новый СжатиеДанных(9));
Исключение
    ЗаписьЖурналаРегистрации("СохранениеВХранилище",
        УровеньЖурналаРегистрации.Ошибка, , ,
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    Отказ = Истина;
КонецПопытки;

С Отказ = Истина объект не запишется наполовину, а причина останется в журнале регистрации.

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

Чтение из хранилища и восстановление данных

Обратная операция — метод Получить(). Он возвращает исходное значение того же типа, что упаковали.

&НаСервере
Функция ПрочитатьСнимок(Ключ)

    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
    |    СнимкиДанных.Данные КАК Данные
    |ИЗ
    |    РегистрСведений.СнимкиДанных КАК СнимкиДанных
    |ГДЕ
    |    СнимкиДанных.Ключ = &Ключ";
    Запрос.УстановитьПараметр("Ключ", Ключ);

    Выборка = Запрос.Выполнить().Выбрать();
    Если Не Выборка.Следующий() Тогда
        Возврат Неопределено;
    КонецЕсли;

    Значение = Выборка.Данные.Получить();
    Если ТипЗнч(Значение) <> Тип("ТаблицаЗначений") Тогда
        Возврат Неопределено;
    КонецЕсли;

    Возврат Значение;

КонецФункции

Две проверки обязательны. Пустой реквизит возвращает Неопределено. А проверка типа защищает от старых записей, где когда-то лежало что-то другое.

Как вернуть данные в рабочие объекты

  • 📋 Табличная часть. Объект.Товары.Загрузить(Таблица) на сервере, если колонки совпадают по именам.
  • 🖼️ Картинка для формы. ПоместитьВоВременноеХранилище(Значение, УникальныйИдентификатор) и адрес в поле картинки.
  • 💾 Файл пользователю. Положите ДвоичныеДанные во временное хранилище, на клиенте вызовите НачатьПолучениеФайлов или ПолучитьФайл.
  • 🔁 Восстановление предыдущей версии. Если хранили снимки по датам, прочитайте нужный и загрузите обратно в объект — так делают простой откат настроек.

Как не утяжелить базу: практика

Хранилище удобно, но легко превращает справочник в мешок с мегабайтами. Вот что помогает.

Выносите файлы в отдельный объект

При чтении объекта платформа читает все его реквизиты, включая хранилище. Если в каждом элементе «Номенклатуры» лежит фото на 2 МБ, то любое Ссылка.ПолучитьОбъект() тянет эти 2 МБ.

Надёжнее держать файлы в отдельном справочнике или регистре сведений с ключом-ссылкой на владельца. В конфигурациях на БСП так и устроено: подсистема «Присоединенные файлы» хранит файлы отдельно от владельца, в базе или на томах на диске.

Подбирайте сжатие под данные

ДанныеЭффект от СжатиеДанных(9)
Текст, XML, JSON, таблицы значенийЗаметный, размер падает в разы
JPEG, PNG, ZIP, PDF со сканамиПочти никакого, файлы уже сжаты
Мелкие структуры настроекНе нужен

Не выбирайте хранилище в массовых запросах

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

Для паролей и токенов — не обычный реквизит

Пароли к сервисам в хранилище обычного реквизита видны любому, у кого есть право чтения объекта. В БСП для этого есть безопасное хранилище: ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище(Владелец, Данные, Ключ) и ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища(Владелец, Ключ). Доступ к нему закрыт для обычных пользователей.

Пользовательские настройки — в хранилище настроек

Если нужно запомнить выбор пользователя (последний склад, фильтр), реквизит не нужен. Используйте ХранилищеОбщихНастроек.Сохранить(КлючОбъекта, КлючНастроек, Значение) и ХранилищеОбщихНастроек.Загрузить(КлючОбъекта, КлючНастроек). Настройки привязываются к пользователю автоматически.

Куда сохранить ваши данные?

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

Можно ли искать по содержимому ХранилищаЗначений в запросе?
Нет. Условия, сортировка и группировка по такому полю запрещены. Если нужен поиск, храните рядом обычный реквизит с ключевыми данными, например имя файла или хеш.
Сколько данных помещается в одно хранилище?
Жёсткого лимита в самом типе нет, ограничивают СУБД и память сервера. На практике файлы больше 50–100 МБ лучше хранить на диске или в томах, а в базе — только ссылку.
Как узнать размер того, что лежит в хранилище?
Если там двоичные данные, получите их и вызовите метод Размер(): он вернёт число байт. Для других типов размер упакованного значения напрямую не узнать.
Можно ли выгрузить хранилище в XML или JSON?
Да, через сериализацию: в XML хранилище записывается как строка в кодировке Base64. При обмене между базами такое поле переносится без доработки.
Почему после обновления конфигурации картинки пропали?
Обычно реквизит сменил тип или имя. Данные из удалённого реквизита при реструктуризации теряются, поэтому перед изменением типа сделайте резервную копию базы.