Хранилище значения 1С — тип ХранилищеЗначения, который упаковывает сериализуемое значение (двоичные данные файла, картинку, таблицу значений, структуру настроек), чтобы записать его в реквизит, ресурс регистра сведений или константу. Ниже — сжатие, метод Получить(), реквизит объекта, картинка на форме, поиск, выгрузка в файл и сравнение хранилищ. Примеры для платформы 8.3, имена справочников, регистров и реквизитов в листингах условные.
Реквизиту справочника или документа нельзя задать тип ТаблицаЗначений, Структура или Картинка. Для таких значений и для содержимого файлов в метаданных есть тип ХранилищеЗначения: платформа сериализует значение в двоичный блок, кладёт его в поле таблицы базы и при чтении собирает обратно. Тип доступен для реквизитов объектов, ресурсов и реквизитов регистров сведений, констант.
С временным хранилищем его не путайте: ПоместитьВоВременноеХранилище служит для передачи данных между клиентом и сервером и разобран в статье про двоичные данные.
| Что сохранить | Где держать |
|---|---|
| Настройки, небольшие таблицы и структуры | Реквизит ХранилищеЗначения со сжатием |
| Логотип, миниатюра до сотни килобайт | Реквизит или константа ХранилищеЗначения без сжатия |
| Сканы, фото, документы от мегабайта | Отдельный регистр сведений или присоединённые файлы БСП, в том числе в томах на диске |
| Файл на время одной операции | Временное хранилище |
Чтобы поместить значение в хранилище, его передают в конструктор вместе с необязательным объектом СжатиеДанных. Метод Получить() возвращает сохранённое.
// Модуль формы, условный пример. В тонком и веб-клиенте типа ХранилищеЗначения нет,
// поэтому клиентская команда только вызывает серверную процедуру.
&НаКлиенте
Процедура ПроверитьХранилище(Команда)
ПримерыХранилищаНаСервере();
КонецПроцедуры
&НаСервере
Процедура ПримерыХранилищаНаСервере()
// 1. Двоичные данные файла. PNG, JPEG, ZIP и DOCX уже сжаты, СжатиеДанных не нужно
ДанныеФайла = Новый ДвоичныеДанные("D:\Обмен\logo.png"); // файл на сервере, путь условный
ХранилищеФайла = Новый ХранилищеЗначения(ДанныеФайла);
// 2. Картинка тоже сериализуется: Получить() вернёт объект Картинка
ХранилищеКартинки = Новый ХранилищеЗначения(Новый Картинка(ДанныеФайла));
Сообщить("Тип содержимого: " + ТипЗнч(ХранилищеКартинки.Получить()));
// 3. Таблица значений хорошо сжимается, 9 — максимальный уровень
Цены = Новый ТаблицаЗначений;
Цены.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка"));
Цены.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
Для Номер = 1 По 1000 Цикл
НоваяСтрока = Цены.Добавить();
НоваяСтрока.Артикул = "А-" + Формат(Номер, "ЧГ=0");
НоваяСтрока.Цена = Номер * 10;
КонецЦикла;
ХранилищеЦен = Новый ХранилищеЗначения(Цены, Новый СжатиеДанных(9));
// Получить() каждый раз возвращает новую копию: очистка копии хранилище не меняет
Копия = ХранилищеЦен.Получить();
Копия.Очистить();
Сообщить("Строк в хранилище: " + ХранилищеЦен.Получить().Количество()); // 1000
// 4. Хранилище без значения возвращает Неопределено
Пустое = Новый ХранилищеЗначения(Неопределено);
Если Пустое.Получить() = Неопределено Тогда
Сообщить("Хранилище пустое");
КонецЕсли;
// 5. Константа ЛоготипКомпании типа ХранилищеЗначения (условная):
// первый Получить() читает константу, второй достаёт содержимое хранилища
Константы.ЛоготипКомпании.Установить(ХранилищеФайла);
Логотип = Константы.ЛоготипКомпании.Получить().Получить();
Сообщить("Размер логотипа, байт: " + Логотип.Размер());
КонецПроцедурыНовый СжатиеДанных(9) задаёт уровень от 0 до 9, где 9 — максимальное сжатие.Получить() при каждом вызове заново разворачивает данные и отдаёт новую копию. Вызывайте его один раз и работайте с переменной.Неопределено — проверяйте тип результата.Получить(): менеджер константы отдаёт хранилище, хранилище — содержимое.Запрос, МенеджерВременныхТаблиц, ЧтениеXML или COMОбъект в хранилище не поместить.Типовая задача — сохранить картинку в элемент справочника из обработки или фонового задания и прочитать её обратно. Код модуля менеджера:
// Модуль менеджера справочника Номенклатура (имена условные).
// Реквизит Картинка имеет тип ХранилищеЗначения.
Процедура ЗаписатьКартинку(Номенклатура, ДанныеКартинки) Экспорт
Если ТипЗнч(ДанныеКартинки) <> Тип("ДвоичныеДанные") Тогда
ВызватьИсключение "Картинку нужно передать двоичными данными";
КонецЕсли;
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
ЭлементБлокировки.УстановитьЗначение("Ссылка", Номенклатура);
Блокировка.Заблокировать();
Объект = Номенклатура.ПолучитьОбъект();
Объект.Картинка = Новый ХранилищеЗначения(ДанныеКартинки); // JPEG и PNG не сжимаем
Объект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
// Возвращает ДвоичныеДанные картинки или Неопределено, если картинки нет.
Функция КартинкаНоменклатуры(Номенклатура) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Картинка КАК Картинка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Номенклатура);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Картинка.Получить();
КонецЕсли;
Возврат Неопределено;
КонецФункцииТипЗнч держит формат единым: в реквизите всегда ДвоичныеДанные, а не то картинка, то строка Base64.ПолучитьОбъект() ради картинки поднимает весь элемент с табличными частями. В конфигурациях на БСП короче: ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Номенклатура, "Картинка").Получить().Хранилище удобно для настроек обработки или профиля загрузки: структура с параметрами и таблицей колонок лежит в одном реквизите.
// Модуль менеджера справочника ПрофилиЗагрузки (имена условные).
// Реквизит Настройки имеет тип ХранилищеЗначения.
Функция НастройкиПоУмолчанию() Экспорт
Колонки = Новый ТаблицаЗначений;
Колонки.Колонки.Добавить("КолонкаФайла", Новый ОписаниеТипов("Строка"));
Колонки.Колонки.Добавить("Реквизит", Новый ОписаниеТипов("Строка"));
Колонки.Колонки.Добавить("Обязательный", Новый ОписаниеТипов("Булево"));
Настройки = Новый Структура;
Настройки.Вставить("Разделитель", ";");
Настройки.Вставить("ПерваяСтрокаДанных", 2);
Настройки.Вставить("Колонки", Колонки);
Возврат Настройки;
КонецФункции
Функция ПрочитатьНастройки(Профиль) Экспорт
Настройки = НастройкиПоУмолчанию();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПрофилиЗагрузки.Настройки КАК Настройки
|ИЗ
| Справочник.ПрофилиЗагрузки КАК ПрофилиЗагрузки
|ГДЕ
| ПрофилиЗагрузки.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Профиль);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Сохраненные = Выборка.Настройки.Получить();
Если ТипЗнч(Сохраненные) = Тип("Структура") Тогда
// ключи, которых нет в старых настройках, остаются по умолчанию
ЗаполнитьЗначенияСвойств(Настройки, Сохраненные);
КонецЕсли;
КонецЕсли;
Возврат Настройки;
КонецФункции
Процедура СохранитьНастройки(Профиль, Настройки) Экспорт
// для краткости без блокировки; в рабочем коде — как в предыдущем листинге
Объект = Профиль.ПолучитьОбъект();
Объект.Настройки = Новый ХранилищеЗначения(Настройки, Новый СжатиеДанных(9));
Объект.Записать();
КонецПроцедуры
Процедура ДобавитьКолонкуАртикул(Профиль) Экспорт
Настройки = ПрочитатьНастройки(Профиль);
НоваяСтрока = Настройки.Колонки.Добавить();
НоваяСтрока.КолонкаФайла = "Артикул";
НоваяСтрока.Реквизит = "Артикул";
НоваяСтрока.Обязательный = Истина;
// без повторной записи изменение останется только в памяти
СохранитьНастройки(Профиль, Настройки);
КонецПроцедурыЗаполнитьЗначенияСвойств переносит только ключи, которые есть в обеих структурах. Ключ, добавленный в новой версии кода, не сломает чтение старых настроек.Получить() отдал копию: новая строка живёт в памяти, пока СохранитьНастройки не создаст новое хранилище и не запишет объект.Реквизиты типа ХранилищеЗначения в данные управляемой формы не попадают, поэтому картинку берут из ТекущийОбъект в серверных обработчиках. Поле картинки привязано к строковому реквизиту формы АдресКартинки: в нём лежит навигационная ссылка на реквизит записанного объекта или адрес временного хранилища с только что выбранным файлом.
// Модуль формы элемента справочника Номенклатура (имена условные).
// Реквизиты формы: АдресКартинки (Строка) — к нему привязано поле картинки,
// КартинкаУдалена (Булево). Команда выбора файла кладёт новый файл
// во временное хранилище и записывает адрес в АдресКартинки.
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
ПоказатьКартинку(ТекущийОбъект);
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресКартинки) Тогда
ДанныеКартинки = ПолучитьИзВременногоХранилища(АдресКартинки);
ТекущийОбъект.Картинка = Новый ХранилищеЗначения(ДанныеКартинки);
ИначеЕсли КартинкаУдалена Тогда
ТекущийОбъект.Картинка = Новый ХранилищеЗначения(Неопределено);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресКартинки) Тогда
УдалитьИзВременногоХранилища(АдресКартинки);
КонецЕсли;
КартинкаУдалена = Ложь;
ПоказатьКартинку(ТекущийОбъект);
КонецПроцедуры
&НаСервере
Процедура ПоказатьКартинку(ТекущийОбъект)
Если ТекущийОбъект.Картинка.Получить() = Неопределено Тогда
АдресКартинки = "";
Иначе
// ссылка вида e1cib/data/Справочник.Номенклатура.Картинка?ref=...
АдресКартинки = ПолучитьНавигационнуюСсылку(ТекущийОбъект.Ссылка, "Картинка");
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура УдалитьКартинку(Команда)
АдресКартинки = "";
КартинкаУдалена = Истина;
Модифицированность = Истина;
КонецПроцедурыПолучитьНавигационнуюСсылку(ТекущийОбъект.Ссылка, "Картинка") возвращает ссылку на реквизит: поле само запрашивает картинку у сервера, двоичные данные не проходят через модуль формы.ПередЗаписьюНаСервере выбранный файл превращается в хранилище, а Новый ХранилищеЗначения(Неопределено) очищает реквизит.ПослеЗаписиНаСервере освобождает временное хранилище и переключает поле на навигационную ссылку.В таблице СУБД реквизит-хранилище — двоичное поле неограниченной длины с сериализованным и, возможно, сжатым значением. СУБД не знает, что внутри таблица или PNG, поэтому в запросе такое поле можно только выбрать. В условиях ГДЕ и СОЕДИНЕНИЕ ... ПО, в УПОРЯДОЧИТЬ ПО, СГРУППИРОВАТЬ ПО, РАЗЛИЧНЫЕ и агрегатных функциях оно не работает, индекс по нему не построить. ОБЪЕДИНИТЬ без ВСЕ тоже сравнивает строки, поэтому для выборок с хранилищем нужен ОБЪЕДИНИТЬ ВСЕ.
Решение — хранить рядом с содержимым поля, по которым ищут: имя файла, размер, дату загрузки и хеш. Хеш SHA-256 — строка из 64 символов, её можно индексировать и сравнивать обычным равенством. Заодно картинки вынесены из справочника в отдельный регистр сведений.
// Модуль менеджера регистра сведений КартинкиНоменклатуры (имена условные).
// Измерение Номенклатура, ресурс Данные (ХранилищеЗначения),
// реквизиты ИмяФайла (Строка 255), Размер (Число 15), Хеш (Строка 64, индексировать).
Процедура ЗаписатьКартинку(Номенклатура, ДанныеФайла, ИмяФайла) Экспорт
МенеджерЗаписи = РегистрыСведений.КартинкиНоменклатуры.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Номенклатура = Номенклатура;
МенеджерЗаписи.Данные = Новый ХранилищеЗначения(ДанныеФайла);
МенеджерЗаписи.ИмяФайла = ИмяФайла;
МенеджерЗаписи.Размер = ДанныеФайла.Размер();
МенеджерЗаписи.Хеш = ХешДанных(ДанныеФайла);
МенеджерЗаписи.Записать(); // запись с тем же измерением замещается
КонецПроцедуры
// По хранилищу отбирать нельзя, поэтому дубликат ищем по хешу содержимого.
Функция НайтиПоСодержимому(ДанныеФайла) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КартинкиНоменклатуры.Номенклатура КАК Номенклатура,
| КартинкиНоменклатуры.ИмяФайла КАК ИмяФайла,
| КартинкиНоменклатуры.Размер КАК Размер
|ИЗ
| РегистрСведений.КартинкиНоменклатуры КАК КартинкиНоменклатуры
|ГДЕ
| КартинкиНоменклатуры.Хеш = &Хеш";
Запрос.УстановитьПараметр("Хеш", ХешДанных(ДанныеФайла));
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Функция ХешДанных(ДанныеФайла)
Хеширование = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеширование.Добавить(ДанныеФайла);
Возврат ПолучитьHexСтрокуИзДвоичныхДанных(Хеширование.ХешСумма);
КонецФункцииХеш находит дубликат картинки, не читая ни одного файла.Данные выбирайте только там, где нужен сам файл: каждая такая строка результата тянет его целиком.Хранилище лежит в той же строке таблицы, что и остальные реквизиты объекта. Отсюда последствия:
ПолучитьОбъект(), открытие формы элемента и групповые обработки читают файл целиком, даже если нужен один артикул;Сканы, фото и документы по нескольку мегабайт выносите в отдельный регистр сведений, как в листинге выше, или в присоединённые файлы БСП. Подсистема «Работа с файлами» хранит содержимое отдельно от карточки, умеет класть файлы в тома на диске, ведёт версии и права. Так устроены картинки номенклатуры в КА 2 и УТ 11: реквизит ФайлКартинки ссылается на присоединённый файл, а не хранит картинку сам.
Для обмена, архива или фонового задания содержимое пишут в файл на сервере. Двоичные данные записываются как есть, остальные значения — в XML через сериализатор.
// Общий модуль РаботаСХранилищем с флажком «Сервер» (имя условное).
// Пишет содержимое хранилища в файл на сервере. Возвращает Ложь, если хранилище пустое.
Функция ВыгрузитьВФайл(Хранилище, ПолноеИмяФайла) Экспорт
Значение = Хранилище.Получить();
Если Значение = Неопределено Тогда
Возврат Ложь;
ИначеЕсли ТипЗнч(Значение) = Тип("ДвоичныеДанные") Тогда
Значение.Записать(ПолноеИмяФайла);
ИначеЕсли ТипЗнч(Значение) = Тип("Картинка") Тогда
Значение.ПолучитьДвоичныеДанные().Записать(ПолноеИмяФайла);
Иначе
// таблица значений, структура и другие сериализуемые значения — в XML
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ПолноеИмяФайла, "UTF-8");
Попытка
ЗаписьXML.ЗаписатьОбъявлениеXML();
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, Значение, НазначениеТипаXML.Явное);
Исключение
ЗаписьXML.Закрыть();
ВызватьИсключение;
КонецПопытки;
ЗаписьXML.Закрыть();
КонецЕсли;
Возврат Истина;
КонецФункции
// Пример: настройки профиля из листинга выше — во временный XML-файл на сервере.
Функция ВыгрузитьНастройкиПрофиля(Профиль) Экспорт
Хранилище = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Профиль, "Настройки"); // БСП
Если ТипЗнч(Хранилище) <> Тип("ХранилищеЗначения") Тогда
Возврат "";
КонецЕсли;
ИмяФайла = ПолучитьИмяВременногоФайла("xml");
Если ВыгрузитьВФайл(Хранилище, ИмяФайла) Тогда
Возврат ИмяФайла;
КонецЕсли;
Возврат "";
КонецФункцииЧтениеXML и СериализаторXDTO.ПрочитатьXML.Попытка гарантирует, что файл XML закроется и при ошибке сериализации.Оператор = сравнивает хранилища как объекты, а не их содержимое, поэтому даже БСП в ОбщегоНазначения.ДанныеСовпадают разворачивает их через Получить(). Надёжнее сравнить хеши содержимого.
// Общий модуль РаботаСХранилищем с флажком «Сервер» (имя условное).
// Хеш содержимого не зависит от уровня сжатия, с которым создано хранилище.
Функция ХешСодержимого(Хранилище) Экспорт
Значение = Хранилище.Получить();
Хеширование = Новый ХешированиеДанных(ХешФункция.SHA256);
Если ТипЗнч(Значение) = Тип("ДвоичныеДанные") Тогда
Хеширование.Добавить(Значение);
Иначе
Хеширование.Добавить(ЗначениеВСтрокуВнутр(Значение));
КонецЕсли;
Возврат ПолучитьHexСтрокуИзДвоичныхДанных(Хеширование.ХешСумма);
КонецФункции
Функция ХранилищаРавны(Хранилище1, Хранилище2) Экспорт
Возврат ХешСодержимого(Хранилище1) = ХешСодержимого(Хранилище2);
КонецФункции
// Пример: перезаписать настройки профиля, только если они изменились.
Процедура ОбновитьНастройки(Профиль, НовыеНастройки) Экспорт
НовоеХранилище = Новый ХранилищеЗначения(НовыеНастройки, Новый СжатиеДанных(9));
Объект = Профиль.ПолучитьОбъект();
Если ХранилищаРавны(Объект.Настройки, НовоеХранилище) Тогда
Возврат; // объект не пишем: нет лишней версии и регистрации к обмену
КонецЕсли;
Объект.Настройки = НовоеХранилище;
Объект.Записать();
КонецПроцедурыЗначениеВСтрокуВнутр. Сравнение строгое: таблица с теми же строками в другом порядке считается другой.&НаКлиенте. В тонком и веб-клиенте этого типа нет: создавайте и разбирайте хранилище на сервере, а на клиент передавайте ДвоичныеДанные или адрес временного хранилища.Получить() вызвали у NULL: поле хранилища пришло через ЛЕВОЕ СОЕДИНЕНИЕ, а записи в регистре нет. Перед вызовом проверьте ТипЗнч(Выборка.Данные) = Тип("ХранилищеЗначения").Объект.Картинка. Реквизит-хранилище в данные формы не входит — работайте через ТекущийОбъект, как в листинге с картинкой.Получить() и записали объект, но реквизиту не присвоили новое хранилище — в базе осталась старая таблица.ГДЕ или УПОРЯДОЧИТЬ ПО не выполняется. Текст ошибки зависит от СУБД и режима работы базы, а решение одно — условие по хешу, имени или размеру файла.На сервере вызовите Получить() у реквизита и проверьте тип: Таблица = Выборка.Настройки.Получить();, затем Если ТипЗнч(Таблица) = Тип("ТаблицаЗначений") Тогда. Пустое хранилище вернёт Неопределено. Изменённую таблицу записывайте обратно через новое хранилище.
ХранилищеЗначения записывается в базу и живёт вместе с объектом. Временное хранилище — служебная область сеанса для передачи данных между клиентом и сервером, его адрес в реквизит не сохраняют.
Предел задают СУБД и память сервера: значение целиком проходит через рабочий процесс при каждой записи и чтении. Файлы от мегабайта держите в отдельном регистре или в присоединённых файлах с томами на диске.
JPEG, PNG и офисные форматы DOCX и XLSX уже сжаты, повторное сжатие почти ничего не даёт. СжатиеДанных полезно для таблиц значений, структур, XML и текста.
Перенос накопленных картинок и файлов из реквизитов в регистр или тома на диске без потери данных и простоя — типовая доработка из раздела отчёты и обработки 1С. Опишите конфигурацию, объём базы и что нужно перенести — разместите задание, и исполнители оценят сроки и цену.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.