Копирование ХранилищаЗначения в 1С: присваивание, клон и выгрузка в файл

· 9 мин

Содержание

Частая ошибка: человек пытается «скопировать» ХранилищеЗначения как структуру или таблицу, обходит его в цикле и ничего не находит. Внутри хранилища нет ни полей, ни строк. Это упакованный в двоичный вид объект, и обращаться к нему надо через два действия: положить значение в конструктор и достать методом Получить().

Чтобы скопировать хранилище значения в 1С, достаточно присвоить его другому реквизиту, если вам нужна одна и та же упаковка. Если нужна самостоятельная копия с другим содержимым или сжатием, распакуйте данные методом Получить() и создайте новое хранилище через Новый ХранилищеЗначения(...). Ниже оба способа, а ещё пустые значения, сериализация и работа на клиенте.

Что внутри хранилища и почему его не копируют «по строкам»

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

Главные свойства, которые влияют на копирование:

  • 🔒 хранилище нельзя изменить «на месте»: нет методов добавить или удалить элемент;
  • 📦 единственный способ прочитать содержимое — метод Получить();
  • 🧱 единственный способ записать — создать новое хранилище конструктором;
  • ⚙️ внутри лежит копия значения на момент упаковки, а не ссылка на исходный объект.

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

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

Способ 1. Присваивание: когда копия не нужна

Самый простой вариант. Вы присваиваете хранилище переменной или реквизиту:

НовоеХранилище = ИсходныйОбъект.Данные;
ДругойОбъект.Данные = НовоеХранилище;

Так как хранилище неизменяемое, обе переменные безопасно указывают на одно и то же содержимое. Изменить его через одну переменную нельзя, поэтому «испортить» вторую вы не сможете. Любое изменение — это новое хранилище, которое вы присвоите обратно.

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

Новый = Источник.Скопировать();
Новый.Данные = Источник.Данные;
Новый.Записать();

Если вы используете стандартную команду копирования элемента справочника в интерфейсе, реквизит типа ХранилищеЗначения копируется вместе с остальными реквизитами. Отдельно ничего делать не надо.

Способ 2. Распаковать и упаковать заново

Нужен, когда вы хотите изменить содержимое перед копированием, сменить степень сжатия или проверить, что данные читаются. Алгоритм из трёх действий:

  1. Достаньте значение методом Получить().
  2. При необходимости измените его.
  3. Создайте новое хранилище конструктором.
Данные = Источник.Данные.Получить();

Если Данные = Неопределено Тогда
    Приемник.Данные = Новый ХранилищеЗначения(Неопределено);
Иначе
    Приемник.Данные = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));
КонецЕсли;

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

СпособКогда использоватьРиски
ПрисваиваниеПеренести хранилище как естьНет, хранилище неизменяемое
Получить() + новый конструкторИзменить данные или сжатиеРаспаковка тяжёлых данных занимает память
Выгрузка в файлПередать данные в другую базуНужна совместимая конфигурация при загрузке

Клонирование таблицы значений из хранилища

Типичный случай — «клонирование» таблицы значений, которая лежит в хранилище. Нужна независимая копия, которую можно править, и в итоге новое хранилище:

Таблица = Источник.Данные.Получить();

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

Здесь важны две вещи. Метод Скопировать() у таблицы значений создаёт независимую копию, и правки не затронут исходное хранилище. Проверка ТипЗнч защищает от ситуации, когда в хранилище лежит не то, что вы ждёте.

Если вы записываете в хранилище результат запроса, превращайте его в таблицу значений через Выгрузить(). Сам РезультатЗапроса сериализации не подлежит.

Пустые и неопределённые значения

Здесь больше всего ловушек. Реквизит типа ХранилищеЗначения у нового объекта не Неопределено: платформа создаёт пустое хранилище. Но метод Получить() у него вернёт Неопределено.

Поэтому проверять надо результат, а не само хранилище:

Значение = Объект.Данные.Получить();
Если Значение = Неопределено Тогда
    // данных нет: хранилище пустое
КонецЕсли;

Пустое хранилище создают так:

Пустое = Новый ХранилищеЗначения(Неопределено);

Что учесть:

  • ❌ проверка Объект.Данные = Неопределено не сработает, потому что хранилище есть всегда;
  • ❌ вызов Получить() у реквизита, который не заполнен, не вызывает ошибку, а даёт Неопределено;
  • ✅ перед обращением к полям полученного значения проверяйте ТипЗнч, иначе получите «Поле объекта не обнаружено».

Если вы копируете хранилище в цикле по большому справочнику, пустые значения можно пропускать. Так вы не создаёте лишних хранилищ и не раздуваете базу.

Сериализация и выгрузка в файл для обмена

Чтобы передать данные в другую базу, сериализуйте объект. Хранилище значения поддерживает XML-сериализацию, поэтому его можно выгрузить вместе с остальными реквизитами.

Самый короткий путь — записать значение в файл и прочитать обратно:

ЗначениеВФайл("C:\Обмен\данные.val", Данные);
// на другой стороне
Данные = ЗначениеИзФайла("C:\Обмен\данные.val");

Для обмена в формате XML используйте сериализатор:

ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\Обмен\данные.xml");
ЗаписьXML.ЗаписатьОбъявлениеXML();
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, Объект.Данные);
ЗаписьXML.Закрыть();

Если нужен именно двоичный файл — например, чтобы сохранить вложение из хранилища, — достаньте данные и запишите их:

ДвоичныеДанные = Объект.Данные.Получить();
Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные") Тогда
    ДвоичныеДанные.Записать("C:\Обмен\вложение.bin");
КонецЕсли;

Две особенности:

  • 📁 путь должен быть доступен тому процессу, который выполняет код. На сервере это каталог сервера, а не папка на вашем компьютере;
  • 🔁 при загрузке в другую базу типы внутри хранилища должны существовать и там. Таблица значений с произвольными колонками прочитается везде, а ссылки на справочники — только если такой справочник есть в приёмнике.

Прежде чем массово перезаписывать реквизиты-хранилища в рабочей базе, сделайте копию базы: исходное содержимое после перезаписи не вернуть.

Тонкий клиент, веб-клиент и сервер

Хранилище значения — тип, который удобнее всего обрабатывать на сервере. На клиенте работать с ним можно, но тяжёлые данные гонять туда-сюда не стоит.

Практические правила:

  1. Распаковку и упаковку большого содержимого выполняйте в серверной процедуре с директивой &НаСервере.
  2. Если нужно отдать данные клиенту — например, предложить сохранить файл , — поместите двоичные данные во временное хранилище через ПоместитьВоВременноеХранилище и передайте на клиент только адрес.
  3. В веб-клиенте прямая запись в произвольную папку диска недоступна. Файл сохраняют через диалог браузера: сервер готовит двоичные данные, клиент вызывает получение файла по адресу.
  4. В реквизите формы тип ХранилищеЗначения отдельно не отображается. Чтобы показать пользователю содержимое, выводите распакованные данные.

Если вы пишете обработку для копирования между базами, учтите, что в клиент-серверном варианте работа тяжёлая по трафику: каждое обращение Получить() на клиенте тянет данные по сети.

Массовое копирование по справочнику

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

Порядок работы такой:

  1. Выберите ссылки на элементы-источники запросом к справочнику.
  2. В цикле получайте объект методом ПолучитьОбъект().
  3. Копируйте реквизит присваиванием или через Получить() и новый конструктор.
  4. Записывайте объект и переходите к следующему.
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Источник = Выборка.Ссылка.ПолучитьОбъект();
    Приемник = Справочники.Номенклатура.СоздатьЭлемент();
    Приемник.Наименование = Источник.Наименование + " (копия)";
    Приемник.Данные = Источник.Данные;
    Приемник.Записать();
КонецЦикла;

Для пакета в тысячи элементов запускайте код фоновым заданием. Так вы не держите интерактивный сеанс, а при сбое потеряете не всё: записанные элементы останутся.

Если в хранилищах лежат тяжёлые вложения, копируйте порциями, например по 500 элементов, и пишите в журнал, на каком элементе остановились. Повторный запуск продолжит с места остановки.

Типичные ошибки при работе с хранилищем

Большинство сбоев сводится к четырём ситуациям. Сверьтесь со списком, прежде чем искать причину в платформе.

  • 🚫 Ожидание Неопределено от реквизита. Хранилище есть всегда, пустым бывает только то, что вернул Получить().
  • 🚫 Обращение к свойствам без проверки типа. Если вместо структуры лежит строка или двоичные данные, получите ошибку «Поле объекта не обнаружено».
  • 🚫 Попытка сериализовать несовместимое значение. В конструктор нельзя передать значение, которое не поддерживает сериализацию. Платформа выдаст исключение при создании хранилища.
  • 🚫 Запись из клиента без сервера. На клиенте конструктор отработает, но потом вы будете передавать большой объём данных по сети и ждать.
Диагностика: выберите симптом

Проверка размера и содержимого перед копированием

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

Значение = Источник.Данные.Получить();
Сообщить("Тип: " + Строка(ТипЗнч(Значение)));
Если ТипЗнч(Значение) = Тип("ДвоичныеДанные") Тогда
    Сообщить("Размер, байт: " + Формат(Значение.Размер(), "ЧГ=0"));
КонецЕсли;

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

После копирования сверьте результат: выберите несколько случайных элементов, распакуйте хранилища у источника и приёмника и сравните тип и размер. Совпали — копия прошла корректно. Расхождение в размере у сжатых данных нормально, если вы меняли степень сжатия; различие в типе означает ошибку в алгоритме копирования, и пакет лучше остановить.

Чек-лист перед копированием

Перед копированием хранилища
Выполнено: 0 / 5

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

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