ТаблицаЗначений внутри ХранилищеЗначения: запись в реквизит, передача через форму и сериализация в поток

· 9 мин

Содержание

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

Сложности начинаются не на этой строке, а вокруг неё. Таблица значений не существует на тонком клиенте, реквизит-хранилище не попадает в данные формы, а несериализуемое значение в одной ячейке срывает всю запись. Ниже код для каждого из этих случаев и сравнение способов.

Как устроено ХранилищеЗначения и что в него можно положить

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

Что важно знать до написания кода:

  • 📦 Хранится копия. После Новый ХранилищеЗначения(ТЗ) изменения в исходной таблице ТЗ на содержимое хранилища уже не влияют.
  • 🔁 Каждый Получить() десериализует заново. Вызов в цикле — это N полных распаковок. Получите таблицу один раз в переменную.
  • 🧩 Внутри должны быть сериализуемые значения. Примитивы, ссылки, ТаблицаЗначений, Структура, Массив, ДеревоЗначений, ДвоичныеДанные, ТабличныйДокумент — подходят. COMОбъект, открытые файлы, ЗаписьXML, элементы формы — нет.
  • 🔗 Ссылки сохраняются как ссылки. Если элемент справочника потом удалят, после распаковки в ячейке будет битая ссылка.
  • 🚫 В запросе содержимое не видно. По полю типа ХранилищеЗначения нельзя отбирать и сортировать. Если таблицу нужно анализировать отчётами — храните её в табличной части или регистре, а не в хранилище.

Второй параметр конструктора — сжатие. Новый СжатиеДанных(9) даёт максимальное сжатие, Новый СжатиеДанных(0) или отсутствие параметра — хранение без сжатия. Уровни от 0 до 9.

Таблицы с повторяющимися значениями (одинаковые ссылки, нули, пустые строки) сжимаются в разы. Для таблицы на несколько тысяч строк уровень 9 почти всегда оправдан.

Сохранение таблицы значений в хранилище напрямую

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

Запись в реквизит справочника или документа

Пусть у справочника НастройкиОбмена есть реквизит ДанныеТаблицы типа ХранилищеЗначения.

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

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

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

Чтение обратно

&НаСервере
Функция ПрочитатьТаблицуИзНастройки(Ссылка)

    Хранилище = Ссылка.ДанныеТаблицы;
    Если Хранилище = Неопределено Тогда
        Возврат Неопределено;
    КонецЕсли;

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

    Возврат ТЗ;

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

Проверка ТипЗнч здесь не формальность. У нового элемента хранилище пустое, и Получить() вернёт Неопределено. А если в реквизит раньше клали структуру, код дальше упадёт на обращении к Колонки.

Запись в регистр сведений

Ресурс или реквизит регистра сведений тоже может иметь тип ХранилищеЗначения. Измерение — нет.

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

Регистр удобен как «кэш таблиц»: одно измерение-ключ, один ресурс-хранилище, запись перезаписывается по ключу.

Таблица значений в управляемой форме и временное хранилище

На тонком клиенте и в веб-клиенте типа ТаблицаЗначений нет. В форме таблица представлена реквизитом формы, а на клиенте вы работаете с коллекцией данных формы. Поэтому всё, что связано с ХранилищеЗначения и таблицей, делайте в процедурах &НаСервере.

Сохранение при записи объекта

Типовой приём — не писать объект отдельной командой, а встроиться в штатную запись формы. Реквизит формы Товары имеет тип ТаблицаЗначений, реквизит объекта ДанныеТаблицы — тип ХранилищеЗначения.

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

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

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

&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)

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

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

Колонки реквизита формы Товары должны быть заданы в конфигураторе и совпадать по именам с колонками сохранённой таблицы. Лишние колонки из хранилища ЗначениеВРеквизитФормы не создаст.

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

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

Когда таблицу формирует один серверный вызов, а использует другой (например, после диалога на клиенте), её кладут во временное хранилище:

&НаСервере
Функция ПодготовитьТаблицу()

    ТЗ = СформироватьТаблицу(); // ваша процедура
    Возврат ПоместитьВоВременноеХранилище(ТЗ, УникальныйИдентификатор);

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

Клиент хранит только строку-адрес и передаёт её в следующий серверный вызов, где таблицу достают через ПолучитьИзВременногоХранилища(Адрес).

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

Временное хранилище — не место для долгого хранения. Перезапуск сеанса или закрытие формы — и данных нет. Для сохранения между сеансами нужен реквизит или регистр.

Сериализация таблицы через ПотокВПамяти и XML

Прямое хранилище использует внутренний формат платформы. Он удобен, но читать его умеет только 1С. Если таблицу нужно отдать во внешнюю систему, сохранить в файл или передать в другую базу через веб-сервис — сериализуйте её в XML.

Поток в памяти (ПотокВПамяти) избавляет от временных файлов на диске сервера. Объект появился в платформе 8.3.9.

Запись таблицы в двоичные данные

&НаСервере
Функция ТаблицуВДвоичныеДанные(ТЗ)

    Поток = Новый ПотокВПамяти;
    ЗаписьXML = Новый ЗаписьXML;
    ЗаписьXML.ОткрытьПоток(Поток);
    СериализаторXDTO.ЗаписатьXML(ЗаписьXML, ТЗ, НазначениеТипаXML.Явное);
    ЗаписьXML.Закрыть();

    Возврат Поток.ЗакрытьИПолучитьДвоичныеДанные();

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

Полученные ДвоичныеДанные можно сразу положить в хранилище со сжатием: XML разрастается сильно, а жмётся хорошо.

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

Чтение таблицы из двоичных данных

&НаСервере
Функция ТаблицуИзДвоичныхДанных(Данные)

    Поток = Данные.ОткрытьПотокДляЧтения();
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.ОткрытьПоток(Поток);
    ТЗ = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
    ЧтениеXML.Закрыть();
    Поток.Закрыть();

    Возврат ТЗ;

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

Параметр НазначениеТипаXML.Явное записывает тип каждого значения в атрибут XML. Без него при чтении числа и даты из ячеек колонок без заданного типа могут прийти строками.

Вариант через строку

Для коротких таблиц и отладки проще работать со строкой: ЗаписьXML.УстановитьСтроку(), затем ТекстXML = ЗаписьXML.Закрыть(). Для чтения — ЧтениеXML.УстановитьСтроку(ТекстXML). Строку удобно смотреть в отладчике, но на больших объёмах она занимает больше памяти, чем поток.

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

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

СпособКогда подходитОграничения
Новый ХранилищеЗначения(ТЗ) в реквизитеХранить таблицу в той же базе между сеансамиФормат читает только 1С; содержимое не видно в запросах
То же со СжатиеДанных(9)Таблицы от нескольких сотен строкНемного больше времени процессора на запись и чтение
ПоместитьВоВременноеХранилищеПередать таблицу между вызовами сервера в рамках формыДанные живут до закрытия формы или удаления
XML через ПотокВПамятиФайл, веб-сервис, другая база, внешняя системаОбъём XML в разы больше; нужны сериализуемые в XDTO типы
ЗначениеВСтрокуВнутрБыстрый текстовый слепок, отладкаВнутренний формат, не для обмена с внешними системами
Табличная часть или регистрДанные нужно фильтровать, сворачивать, строить отчётыНужна доработка структуры метаданных

Последняя строка — честная альтернатива. Если таблица становится «главными данными» документа и по ней нужен отбор, хранилище — неверный выбор.

Хранилище настроек вместо своего реквизита

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

ХранилищеОбщихНастроек.Сохранить("ЗагрузкаПрайса", "СопоставлениеКолонок", ТЗ);
ТЗ = ХранилищеОбщихНастроек.Загрузить("ЗагрузкаПрайса", "СопоставлениеКолонок");

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

Ошибки при сохранении и восстановление данных из хранилища

Почти все проблемы сводятся к четырём ситуациям.

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

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

  • 🧪 Проверяйте тип после Получить(). Код из раздела про чтение выше — минимальная защита.
  • 🏷️ Храните версию формата. Кладите в хранилище не голую таблицу, а структуру: Новый Структура("Версия, Таблица", 2, ТЗ). Когда набор колонок изменится, по номеру версии старые данные легко отличить и преобразовать.
  • 📋 Проверяйте колонки. Перед ЗначениеВРеквизитФормы сверяйте, что нужные колонки есть: ТЗ.Колонки.Найти("Номенклатура") <> Неопределено.
  • 🔄 Пишите в транзакции. Если вместе с хранилищем меняются и другие данные, записывайте объект и связанные записи в одной транзакции, чтобы не получить таблицу без пары.

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

Большие объёмы: скорость и размер хранилища

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

Что ускоряет работу

  1. Удалите лишние колонки до записи: ТЗ.Колонки.Удалить("Служебная"). Или сразу сделайте копию с нужными колонками: ТЗ.Скопировать(, "Номенклатура, Количество, Сумма").
  2. Сверните повторы, если детализация не нужна: ТЗ.Свернуть("Номенклатура", "Количество, Сумма").
  3. Задавайте типы колонок при создании: ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число")). Это упрощает разбор при чтении XML.
  4. Включайте сжатие уровнем 9 для хранения и не включайте его для временного хранилища в пределах одного сеанса — там сжатие только тратит время.
  5. Не читайте хранилище в запросе ради одного значения. Поле ХранилищеЗначения в выборке тянет за собой весь двоичный объём. Если нужна только дата расчёта или количество строк — храните их в обычных реквизитах рядом.

Когда разбивать таблицу

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

Размер сохранённого хранилища легко оценить на сервере: сериализуйте таблицу через ТаблицуВДвоичныеДанные и посмотрите Данные.Размер() — результат в байтах.

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

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