ТаблицаЗначений внутри ХранилищеЗначения: запись в реквизит, передача через форму и сериализация в поток
Содержание
Таблицу значений помещают в хранилище одной строкой на сервере: Хранилище = Новый ХранилищеЗначения(ТЗ, Новый СжатиеДанных(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, ТЗ). Когда набор колонок изменится, по номеру версии старые данные легко отличить и преобразовать. - 📋 Проверяйте колонки. Перед
ЗначениеВРеквизитФормысверяйте, что нужные колонки есть:ТЗ.Колонки.Найти("Номенклатура") <> Неопределено. - 🔄 Пишите в транзакции. Если вместе с хранилищем меняются и другие данные, записывайте объект и связанные записи в одной транзакции, чтобы не получить таблицу без пары.
Если таблица в хранилище — единственная копия важных данных (загруженная выписка, расчёт за период), делайте резервную копию базы перед обновлением конфигурации, где меняется структура этой таблицы.
Большие объёмы: скорость и размер хранилища
На таблицах в десятки и сотни тысяч строк заметны три вещи: память рабочего процесса, время сериализации и рост базы.
Что ускоряет работу
- Удалите лишние колонки до записи:
ТЗ.Колонки.Удалить("Служебная"). Или сразу сделайте копию с нужными колонками:ТЗ.Скопировать(, "Номенклатура, Количество, Сумма"). - Сверните повторы, если детализация не нужна:
ТЗ.Свернуть("Номенклатура", "Количество, Сумма"). - Задавайте типы колонок при создании:
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число")). Это упрощает разбор при чтении XML. - Включайте сжатие уровнем 9 для хранения и не включайте его для временного хранилища в пределах одного сеанса — там сжатие только тратит время.
- Не читайте хранилище в запросе ради одного значения. Поле
ХранилищеЗначенияв выборке тянет за собой весь двоичный объём. Если нужна только дата расчёта или количество строк — храните их в обычных реквизитах рядом.
Когда разбивать таблицу
Если одна таблица в хранилище весит десятки мегабайт, каждое открытие формы и каждая запись объекта гоняет эти мегабайты между СУБД и сервером 1С. Разбейте данные по периодам или ключам: одна запись регистра — один месяц или один склад. Читать придётся меньше, а запись не будет блокировать весь массив.
Размер сохранённого хранилища легко оценить на сервере: сериализуйте таблицу через ТаблицуВДвоичныеДанные и посмотрите Данные.Размер() — результат в байтах.