К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как записать таблицу значений в хранилище значений 1С

Практическая инструкция: как поместить таблицу значений в хранилище значений 1С через Новый ХранилищеЗначения, сериализацию и чтение данных обратно.

Таблица значений — объект встроенного языка 1С, который существует только в оперативной памяти сеанса. Чтобы сохранить её между сеансами, передать в фоновое задание или прикрепить к объекту базы данных, таблицу нужно сериализовать — и стандартный механизм для этого в платформе 1С:Предприятие 8.3 — хранилище значения (ValueStorage).

Конструктор Новый ХранилищеЗначения(Значение) принимает сериализуемый объект и упаковывает его во внутренний формат платформы. Таблица значений относится к сериализуемым типам, поэтому записывается напрямую, без преобразования в XML или JSON. Полученное хранилище можно присвоить реквизиту объекта с типом «ХранилищеЗначения», полю регистра сведений или реквизиту формы.

Ниже разобран полный цикл: подготовка таблицы, запись в хранилище, сохранение в базе, чтение обратно, типичные ошибки и ограничения, о которых нужно помнить, чтобы не потерять данные или не раздуть размер информационной базы.

Когда хранилище значений подходит для таблицы, а когда нет

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

Если по данным таблицы нужно строить отчёты, делать отборы в запросах, соединять с другими данными — хранилище не подходит. Содержимое хранилища недоступно языку запросов: это «чёрный ящик», который можно только целиком прочитать. В таких случаях правильнее использовать табличную часть объекта или регистр сведений.

Также учитывайте, что каждое чтение хранилища — это десериализация всего содержимого в память. Таблица на сотни тысяч строк, записанная в хранилище, будет заметно замедлять чтение объекта и увеличивать трафик между сервером 1С и СУБД.

Маршрут решения

Как выбрать способ хранения

Данные нужны только целиком, без отборов и запросовИспользуйте реквизит с типом ХранилищеЗначения
По строкам таблицы нужны отборы, отчёты, соединенияИспользуйте табличную часть или регистр сведений
Таблица нужна только в рамках одного сеансаХраните в реквизите формы, хранилище не нужно
Таблицу нужно передать между сеансами или в фоновое заданиеСериализуйте через ХранилищеЗначения и передайте как параметр

Подготовка таблицы значений перед записью

Перед сериализацией убедитесь, что в таблице нет несериализуемых значений. Таблица значений сама сериализуется, но отдельные ячейки могут содержать объекты, которые не переживут запись: например, COMОбъекты, открытые файловые потоки, элементы форм. При попытке записать такое значение платформа выдаст ошибку сериализации.

Типичная подготовка выглядит так: создаётся таблица, описываются колонки с явными типами, заполняются строки. Явное указание типов колонок через ОписаниеТипов важно — колонка без типа примет любое значение, и в неё легко попадёт несериализуемый объект.

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

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

Полезная проверка перед записью — вызвать ЗначениеВСтрокуВнутр(ТЗ). Если функция отработала без исключения, таблица сериализуема и запись в хранилище пройдёт успешно.

Запись таблицы в хранилище значений

Сама запись выполняется одной строкой — конструктором хранилища. Дальше хранилище присваивается реквизиту объекта, тип которого в конфигураторе задан как «ХранилищеЗначения».

bsl
// Вариант 1: запись в реквизит объекта
ОбъектДокумента = ДокументСсылка.ПолучитьОбъект();
ОбъектДокумента.ДанныеТаблицы = Новый ХранилищеЗначения(ТЗ);
ОбъектДокумента.Записать();

// Вариант 2: запись в регистр сведений
МенеджерЗаписи = РегистрыСведений.СохраненныеНастройки.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Пользователь = ТекущийПользователь;
МенеджерЗаписи.КлючНастройки = "ОтборПоСкладу";
МенеджерЗаписи.Данные = Новый ХранилищеЗначения(ТЗ);
МенеджерЗаписи.Записать();

Обратите внимание на второй параметр конструктора — Новый СжатиеДанных(9). По умолчанию хранилище применяет сжатие, но явное указание уровня сжатия позволяет управлять балансом между размером и скоростью: Новый ХранилищеЗначения(ТЗ, Новый СжатиеДанных(9)). Для больших таблиц максимальное сжатие заметно уменьшает объём в базе.

Чтение таблицы из хранилища

Обратное получение выполняется методом Получить() у значения реквизита. Метод возвращает исходный объект — таблицу значений с сохранённой структурой колонок и данными.

bsl
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ДанныеТаблицы ИЗ Документ.ЗаказПокупателя ГДЕ Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
Выборка = Запрос.Выполнить().Выбрать();

Если Выборка.Следующий() Тогда
    Результат = Выборка.ДанныеТаблицы.Получить();
    Если ТипЗнч(Результат) = Тип("ТаблицаЗначений") Тогда
        Для Каждого Строка Из Результат Цикл
            // работа со строками
        КонецЦикла;
    КонецЕсли;
КонецЕсли;

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

Помните, что полученная таблица — это копия в памяти. Изменения в ней не попадут обратно в базу автоматически: после правки нужно снова создать хранилище и записать объект.

Передача таблицы через хранилище между клиентом и сервером

Таблица значений недоступна на тонком клиенте как тип — там работают только данные формы. Но хранилище значений можно положить во временное хранилище через ПоместитьВоВременноеХранилище() и передать адрес между вызовами. Это стандартный приём для обмена сериализованными данными между клиентским и серверным контекстом.

bsl
&НаСервере
Функция СохранитьТаблицуНаСервере()
    ТЗ = РеквизитФормыВЗначение("ТаблицаНаФорме");
    Хранилище = Новый ХранилищеЗначения(ТЗ);
    Возврат ПоместитьВоВременноеХранилище(Хранилище, УникальныйИдентификатор);
КонецФункции

Хранилище значений также является допустимым типом параметра фонового задания: можно передать Новый ХранилищеЗначения(ТЗ) в ФоновыеЗадания.Выполнить(), а внутри фонового метода извлечь таблицу через Получить(). Это надёжнее, чем передача через ЗначениеВСтрокуВнутр, поскольку не требует ручной обработки строкового представления.

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

Ограничения и риски при работе с хранилищем

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

Второй риск — рост базы. Реквизит с хранилищем, обновляемый при каждом проведении документа, копит объёмные данные, которые не видны в отчётах и не очищаются типовыми средствами. Периодически проверяйте размер таблиц базы средствами СУБД, если хранилища используются массово.

Типичные ошибки
01
Забыть вызвать Записать() у объекта после присвоения хранилища — данные останутся только в памяти и потеряются
02
Читать Получить() без проверки типа — при пустом реквизите получите Неопределено и ошибку обращения к колонкам
03
Пытаться использовать хранилище в запросе с отбором по содержимому — язык запросов не видит внутренности хранилища
04
Записывать таблицу с несериализуемыми значениями в ячейках — ошибка возникнет в момент создания хранилища
05
Менять структуру колонок без обработки старых записей — чтение старых данных вернёт таблицу с прежними колонками

Проверка результата после записи

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

Контрольные точки

Как проверить результат

Структура таблицы после Получить()
Норма

Количество и имена колонок совпадают с записанными

Тревожный признак

Колонок меньше или они другие — читается старая версия данных

Количество строк
Норма

Совпадает с количеством при записи

Тревожный признак

Строк нет — реквизит не был записан или объект не сохранён

Типы значений в ячейках
Норма

Ссылки остаются ссылками, числа — числами

Тревожный признак

Значения превратились в строки — в таблицу попали несериализуемые данные

Размер базы после массовой записи
Норма

Рост соответствует объёму данных

Тревожный признак

Резкий рост — хранилища дублируются или не сжимаются

Альтернативы хранилищу значений

Хранилище — не единственный способ сохранить таблицу. В зависимости от задачи могут быть удобнее другие варианты.

Сравнение способов сохранения таблицы

ХранилищеЗначения

Быстрая запись и чтение целиком; не видно в запросах, нет контроля структуры

Табличная часть объекта

Доступна в запросах и отчётах; привязана к одному объекту, усложняет структуру метаданных

Регистр сведений

Гибкие измерения и отборы; требует проектирования структуры и занимает больше места

ЗначениеВСтрокуВнутр в строковый реквизит

Можно хранить в обычном строковом поле; строка нечитаема, размер ограничен длиной реквизита

JSON через ЗаписьJSON

Читаемый и переносимый формат, подходит для обмена; теряются ссылочные типы, медленнее сериализация

Для внутренних служебных данных хранилище значений обычно оптимально: минимум кода, нативная сериализация, сохранение ссылочных типов. Для обмена с внешними системами предпочтительнее JSON, а для данных, участвующих в аналитике, — регистры.

Итог

Что важно запомнить

Запись таблицы в хранилище — одна строка: Новый ХранилищеЗначения(ТЗ). Чтение — метод Получить() с обязательной проверкой типа. Данные в хранилище недоступны запросам, поэтому способ подходит только для данных, которые читаются целиком.

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

Можно ли хранить в одном реквизите-хранилище несколько таблиц?

Да. Оберните таблицы в структуру или массив — оба типа сериализуемы: Новый ХранилищеЗначения(Новый Структура("Таблица1, Таблица2", ТЗ1, ТЗ2)). При чтении получите структуру и обратитесь к нужной таблице по ключу.

Почему после Получить() таблица пустая, хотя запись выполнялась?

Чаще всего объект не был сохранён после присвоения реквизита — не вызван метод Записать(). Вторая причина: читается другой объект или другая запись регистра. Проверьте, что ссылка при записи и чтении совпадает.

Сохраняются ли ссылки на справочники и документы в таблице?

Да, ссылочные типы сериализуются корректно и после Получить() остаются рабочими ссылками. Но если объект, на который ссылается ячейка, будет удалён из базы, в таблице останется «битая» ссылка — хранилище не контролирует ссылочную целостность.

Есть ли ограничение на размер таблицы в хранилище?

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

Можно ли прочитать содержимое хранилища без кода, например через консоль?

Напрямую содержимое недоступно — это внутренний двоичный формат. Но в консоли кода или через обработку «консоль запросов» с произвольным кодом можно выполнить Получить() и посмотреть результат. Стандартные средства просмотра данных содержимое хранилища не отображают.

Что произойдёт со старыми записями, если изменить состав колонок таблицы?

Старые записи сохранят прежнюю структуру: при Получить() вернётся таблица со старыми колонками. Код чтения должен либо проверять наличие нужных колонок через Найти() у коллекции колонок, либо предусмотреть одноразовую процедуру конвертации старых данных.