Хранилище значений в 1С 8.3: как упаковать таблицу, файл или картинку в реквизит
Содержание
Тип ХранилищеЗначения в 1С 8.3 — это контейнер, который упаковывает почти любое значение (таблицу значений, структуру, массив, двоичные данные файла, картинку, табличный документ) в одно поле базы. Записывают его так: Новый ХранилищеЗначения(Значение). Читают через метод Получить(). Других методов у объекта нет, а изменить содержимое уже созданного хранилища нельзя — только создать новое.
Тип общий для платформы и от конфигурации не зависит. Код ниже одинаково работает в БП 3.0, ЗУП 3.1, УТ 11 и в самописных базах. Хранилище доступно на сервере, в толстом клиенте и во внешнем соединении. На тонком клиенте объекта нет, поэтому все операции с ним выносят в серверные процедуры.
Что хранит тип ХранилищеЗначения и где его применяют
Реквизит справочника или документа принимает ограниченный набор типов: строку, число, дату, булево, ссылки. Таблицу значений или PDF-файл в такой реквизит напрямую не положить. Хранилище значений закрывает этот пробел: платформа сериализует значение во внутренний формат и пишет в базу как двоичный блок.
Где это встречается на практике:
- 🖼️ фото сотрудника или товара в карточке справочника;
- 📎 сканы договоров и других документов, приложенные к объекту;
- 📊 сохранённая таблица расчёта, чтобы потом показать, «из чего сложилась сумма»;
- ⚙️ настройки обработки или обмена в константе: структура с десятком параметров;
- 🧾 готовый табличный документ печатной формы, сохранённый на момент печати.
Тип ХранилищеЗначения можно выбрать для реквизита справочника, документа, табличной части, ресурса или реквизита регистра сведений и для константы. В Конфигураторе это делается в свойствах реквизита: Тип → ХранилищеЗначения.
Какие значения поместить нельзя
В хранилище попадает только то, что поддерживает сериализацию. Объекты-инструменты (например, Запрос, ЧтениеXML, ЗаписьXML) и формы туда не помещаются: на конструкторе возникнет исключение. Если сомневаетесь, проверьте значение заранее через ЗначениеВСтрокуВнутр() в попытке.
Как создать хранилище значений: конструктор и сжатие
Конструктор принимает два параметра:
Хранилище = Новый ХранилищеЗначения(Значение, Сжатие);
| Параметр | Что передать | Обязателен |
|---|---|---|
Значение | любое сериализуемое значение | да |
Сжатие | объект СжатиеДанных с уровнем от 0 до 9 | нет |
Без второго параметра данные пишутся без сжатия. Уровень 9 даёт наименьший объём, но тратит больше процессора на запись. Для большинства задач пишут так:
Хранилище = Новый ХранилищеЗначения(ТаблицаРасчета, Новый СжатиеДанных(9));
Когда сжатие помогает, а когда нет
- ✅ Таблицы значений, структуры, текст, XML, JSON — сжимаются в разы, выигрыш заметный.
- ❌ JPEG, PNG, PDF со сканами, ZIP — уже сжаты. Повторное сжатие почти ничего не даёт, только тратит время.
Для фото и сканов можно писать без СжатиеДанных. Для таблиц и настроек сжатие включайте всегда: база растёт медленнее, резервные копии меньше.
Пустое хранилище создаётся без параметров: Новый ХранилищеЗначения(). Его метод Получить() вернёт Неопределено. Такое же пустое хранилище лежит в новом реквизите, пока туда ничего не записали.
Запись и чтение данных в реквизите хранилища значений
Главное правило: в реквизит кладут не само значение, а объект хранилища. Присваивание СправочникОбъект.ДанныеРасчета = ТаблицаРасчета либо завершится ошибкой, либо не сохранит то, что вы ожидаете.
Запись в объект
&НаСервере
Процедура СохранитьРасчет(Ссылка, ТаблицаРасчета)
СправочникОбъект = Ссылка.ПолучитьОбъект();
СправочникОбъект.ДанныеРасчета = Новый ХранилищеЗначения(ТаблицаРасчета, Новый СжатиеДанных(9));
СправочникОбъект.Записать();
КонецПроцедуры
Для константы:
Настройки = Новый Структура("Каталог, Период", "D:\Обмен", 30);
Константы.НастройкиОбмена.Установить(Новый ХранилищеЗначения(Настройки));
Запись в регистр сведений
Для тяжёлых файлов удобно завести отдельный регистр сведений
: измерение Владелец (ссылка на документ), ресурс Файл типа ХранилищеЗначения. Документ остаётся лёгким, а файл читается только тогда, когда он действительно нужен.
&НаСервере
Процедура ЗаписатьФайлВладельца(Владелец, ДанныеФайла)
МенеджерЗаписи = РегистрыСведений.ФайлыДокументов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Владелец = Владелец;
МенеджерЗаписи.Файл = Новый ХранилищеЗначения(ДанныеФайла);
МенеджерЗаписи.Записать(Истина);
КонецПроцедуры
Параметр Истина в Записать() (он же значение по умолчанию) замещает существующую запись с тем же владельцем. С Ложь повторная запись по тому же измерению завершится ошибкой о дублировании записи, поэтому указывайте параметр явно — так код читается однозначно.
Чтение
ТаблицаРасчета = СправочникОбъект.ДанныеРасчета.Получить();
Если ТаблицаРасчета = Неопределено Тогда
// в реквизит ещё ничего не записывали
КонецЕсли;
Получить() каждый раз возвращает новую копию значения. Поменяли строку в полученной таблице — в базе ничего не изменилось. Чтобы сохранить правку, снова создайте Новый ХранилищеЗначения(ТаблицаРасчета), присвойте реквизиту и запишите объект.
Чтение через запрос
Поле типа ХранилищеЗначения можно выбрать в запросе. В условиях ГДЕ, в сортировке и группировке его использовать нельзя: значения хранилищ не сравниваются.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.ДанныеРасчета КАК ДанныеРасчета
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ТаблицаРасчета = Выборка.ДанныеРасчета.Получить();
КонецЕсли;
Так читать выгоднее, чем Ссылка.ПолучитьОбъект(): запрос вытягивает одно поле, а объект — все реквизиты и табличные части.
Хранилище значений в управляемой форме
Здесь чаще всего спотыкаются. В данные формы реквизит типа ХранилищеЗначения не передаётся: на клиенте его нет, и Объект.Фото из модуля формы не прочитать. Схема работы другая — через временное хранилище и его адрес.
- Добавьте в форму реквизит
АдресФототипаСтрока. - Разместите на форме поле картинки и укажите
АдресФотокак его данные. - При открытии формы на сервере достаньте значение и поместите во временное хранилище.
- Перед записью объекта заберите значение по адресу и упакуйте обратно в
ХранилищеЗначения.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
ДанныеФото = Объект.Ссылка.Фото.Получить();
Если ДанныеФото <> Неопределено Тогда
АдресФото = ПоместитьВоВременноеХранилище(ДанныеФото, УникальныйИдентификатор);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресФото) Тогда
ТекущийОбъект.Фото = Новый ХранилищеЗначения(ПолучитьИзВременногоХранилища(АдресФото));
КонецЕсли;
КонецПроцедуры
ТекущийОбъект в ПередЗаписьюНаСервере — это уже настоящий объект базы, и у него реквизит Фото доступен. Новый файл с диска пользователя загружают на сервер стандартными методами помещения файла во временное хранилище; полученный адрес записывают в АдресФото, и поле картинки сразу показывает изображение.
Передавайте в ПоместитьВоВременноеХранилище уникальный идентификатор формы. Тогда данные живут, пока открыта форма, и не висят в памяти сервера до конца сеанса.
Чем ХранилищеЗначения отличается от других типов данных
Похожие задачи в 1С решают несколькими способами. Выбор зависит от того, сколько живут данные и нужно ли по ним искать.
| Способ | Где живут данные | Когда выбирать | Ограничения |
|---|---|---|---|
ХранилищеЗначения в реквизите | в базе, постоянно | файлы, таблицы, настройки объекта | нет поиска и отбора по содержимому |
| Строка неограниченной длины | в базе, постоянно | текст, JSON, XML, который нужно читать глазами | только текст; отбор и сравнение ограничены |
ДвоичныеДанные | в памяти | промежуточная работа с файлом | в реквизит напрямую не записать, только через хранилище |
| Временное хранилище | на сервере, до конца сеанса или жизни формы | передача данных между клиентом и сервером | в базу не попадает |
Хранилища настроек (ХранилищеОбщихНастроек) | в базе, по пользователю | пользовательские настройки | ключи объекта и настройки, а не данные документа |
| Табличная часть | в базе, постоянно | данные, по которым нужны отчёты | структура задаётся в Конфигураторе |
Практический ориентир: если по данным потом строят отчёт или ставят отбор — это табличная часть или регистр. Хранилище подходит для «чёрного ящика», который целиком сохранили и целиком достали.
Ссылки внутри сохранённой таблицы хранятся как ссылки. Если элемент справочника потом удалили, при чтении в таблице окажется битая ссылка «Объект не найден». Контроль ссылочной целостности на содержимое хранилища не распространяется.
Сериализация хранилища и работа с файлами на диске
Положить файл в хранилище и выгрузить обратно
&НаСервере
Процедура ПриложитьСкан(СправочникОбъект, ПутьКФайлу)
ДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
СправочникОбъект.Скан = Новый ХранилищеЗначения(ДанныеФайла);
СправочникОбъект.ИмяФайлаСкана = "договор.pdf";
КонецПроцедуры
&НаСервере
Процедура ВыгрузитьСкан(Ссылка, КаталогВыгрузки)
ДанныеФайла = Ссылка.Скан.Получить();
Если ТипЗнч(ДанныеФайла) = Тип("ДвоичныеДанные") Тогда
ДанныеФайла.Записать(КаталогВыгрузки + "\" + Ссылка.ИмяФайлаСкана);
КонецЕсли;
КонецПроцедуры
Храните имя и расширение файла в отдельном строковом реквизите. Хранилище помнит только содержимое, и без имени потом неясно, PDF там или DOCX.
Код на сервере работает с файловой системой сервера 1С, а не компьютера пользователя. Путь C:\Сканы\ в клиент-серверной базе ищется на сервере. Файлы пользователя передают через временное хранилище.
Сериализация в строку и XML
Хранилище можно превратить в строку внутреннего формата и обратно:
СтрокаХранилища = ЗначениеВСтрокуВнутр(Хранилище);
Хранилище = ЗначениеИзСтрокиВнутр(СтрокаХранилища);
Для обмена между базами используют XDTO-сериализацию. Хранилище записывается в XML как двоичный блок в Base64:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("D:\Обмен\настройки.xml");
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, Хранилище);
ЗаписьXML.Закрыть();
Обратное чтение — через ЧтениеXML и СериализаторXDTO.ПрочитатьXML(). Если внутри лежат ссылки, база-приёмник должна знать эти объекты, иначе после загрузки получите те же битые ссылки.
Для обмена с внешними системами (сайт, мобильное приложение) внутренний формат 1С не годится. Достаньте значение через Получить() и сериализуйте его сами: двоичные данные — в Base64 через Base64Строка(), структуру — в JSON.
Производительность и частые ошибки с хранилищем значений
Как не замедлить базу
Каждый мегабайт в хранилище — это мегабайт в таблице базы и в каждой резервной копии. Пара практических правил:
- Тяжёлые файлы — в отдельный справочник или регистр сведений. Если хранить скан прямо в документе, он будет читаться при каждом
ПолучитьОбъект()и при обращении через точку от ссылки. В типовых конфигурациях на БСП для этого есть механизм присоединённых файлов, который хранит файлы отдельно от документа. - Читайте запросом только нужное поле.
Ссылка.Скан.Получить()подтягивает объект целиком. В цикле по сотне документов это сотня полных чтений. - Не кладите в хранилище то, по чему нужен отбор. Данные придётся доставать и перебирать на сервере, и отчёт по тысяче документов станет медленным.
- Сжимайте таблицы и текст. Таблица расчёта на 5 000 строк без сжатия весит в разы больше, чем со
СжатиеДанных(9). - Следите за размером. Файловая база хуже переносит рост за счёт двоичных данных, чем клиент-серверная. Если сканы исчисляются тысячами, подумайте о хранении в томах на диске средствами БСП.