Копирование ХранилищаЗначения в 1С: присваивание, клон и выгрузка в файл
Содержание
Частая ошибка: человек пытается «скопировать» ХранилищеЗначения как структуру или таблицу, обходит его в цикле и ничего не находит. Внутри хранилища нет ни полей, ни строк. Это упакованный в двоичный вид объект, и обращаться к нему надо через два действия: положить значение в конструктор и достать методом Получить().
Чтобы скопировать хранилище значения в 1С, достаточно присвоить его другому реквизиту, если вам нужна одна и та же упаковка. Если нужна самостоятельная копия с другим содержимым или сжатием, распакуйте данные методом Получить() и создайте новое хранилище через Новый ХранилищеЗначения(...). Ниже оба способа, а ещё пустые значения, сериализация и работа на клиенте.
Что внутри хранилища и почему его не копируют «по строкам»
ХранилищеЗначения хранит одно значение любого сериализуемого типа: таблицу значений, структуру, массив, соответствие, двоичные данные, картинку. Чаще всего его используют в реквизитах справочников и документов, куда нужно положить «что-то большое»: вложенный файл, сохранённые настройки, снимок данных.
Главные свойства, которые влияют на копирование:
- 🔒 хранилище нельзя изменить «на месте»: нет методов добавить или удалить элемент;
- 📦 единственный способ прочитать содержимое — метод
Получить(); - 🧱 единственный способ записать — создать новое хранилище конструктором;
- ⚙️ внутри лежит копия значения на момент упаковки, а не ссылка на исходный объект.
Последний пункт снимает половину вопросов. Если вы упаковали таблицу значений, а потом изменили исходную таблицу, содержимое хранилища останется прежним.
В хранилище нельзя положить значения, которые не поддерживают сериализацию: например, ссылки на объекты интерфейса или элементы формы. В конструктор такое не пройдёт.
Способ 1. Присваивание: когда копия не нужна
Самый простой вариант. Вы присваиваете хранилище переменной или реквизиту:
НовоеХранилище = ИсходныйОбъект.Данные;
ДругойОбъект.Данные = НовоеХранилище;
Так как хранилище неизменяемое, обе переменные безопасно указывают на одно и то же содержимое. Изменить его через одну переменную нельзя, поэтому «испортить» вторую вы не сможете. Любое изменение — это новое хранилище, которое вы присвоите обратно.
Лишних операций здесь нет: платформа не распаковывает данные и не тратит память. Для вложений в десятки мегабайт это самый дешёвый вариант. Этот способ подходит, когда нужно перенести данные из реквизита одного объекта в реквизит другого: например, при копировании элемента справочника программно:
Новый = Источник.Скопировать();
Новый.Данные = Источник.Данные;
Новый.Записать();
Если вы используете стандартную команду копирования элемента справочника в интерфейсе, реквизит типа ХранилищеЗначения копируется вместе с остальными реквизитами. Отдельно ничего делать не надо.
Способ 2. Распаковать и упаковать заново
Нужен, когда вы хотите изменить содержимое перед копированием, сменить степень сжатия или проверить, что данные читаются. Алгоритм из трёх действий:
- Достаньте значение методом
Получить(). - При необходимости измените его.
- Создайте новое хранилище конструктором.
Данные = Источник.Данные.Получить();
Если Данные = Неопределено Тогда
Приемник.Данные = Новый ХранилищеЗначения(Неопределено);
Иначе
Приемник.Данные = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));
КонецЕсли;
Второй параметр конструктора — степень сжатия. Он необязателен: без него платформа применяет сжатие по умолчанию. Значение 9 даёт максимальное сжатие и экономит место в базе, но упаковка займёт чуть больше времени. Для небольших данных разница незаметна, для таблиц на десятки тысяч строк лучше проверить на копии базы.
| Способ | Когда использовать | Риски |
|---|---|---|
| Присваивание | Перенести хранилище как есть | Нет, хранилище неизменяемое |
Получить() + новый конструктор | Изменить данные или сжатие | Распаковка тяжёлых данных занимает память |
| Выгрузка в файл | Передать данные в другую базу | Нужна совместимая конфигурация при загрузке |
Клонирование таблицы значений из хранилища
Типичный случай — «клонирование» таблицы значений, которая лежит в хранилище. Нужна независимая копия, которую можно править, и в итоге новое хранилище:
Таблица = Источник.Данные.Получить();
Если ТипЗнч(Таблица) = Тип("ТаблицаЗначений") Тогда
Копия = Таблица.Скопировать();
// правим Копию как обычную таблицу
Для Каждого Строка Из Копия Цикл
Строка.Количество = Строка.Количество * 2;
КонецЦикла;
Приемник.Данные = Новый ХранилищеЗначения(Копия, Новый СжатиеДанных(9));
КонецЕсли;
Здесь важны две вещи. Метод Скопировать() у таблицы значений создаёт независимую копию, и правки не затронут исходное хранилище. Проверка ТипЗнч защищает от ситуации, когда в хранилище лежит не то, что вы ждёте.
Если вы записываете в хранилище результат запроса, превращайте его в таблицу значений через Выгрузить(). Сам РезультатЗапроса сериализации не подлежит.
Пустые и неопределённые значения
Здесь больше всего ловушек. Реквизит типа ХранилищеЗначения у нового объекта не Неопределено: платформа создаёт пустое хранилище. Но метод Получить() у него вернёт Неопределено.
Поэтому проверять надо результат, а не само хранилище:
Значение = Объект.Данные.Получить();
Если Значение = Неопределено Тогда
// данных нет: хранилище пустое
КонецЕсли;
Пустое хранилище создают так:
Пустое = Новый ХранилищеЗначения(Неопределено);
Что учесть:
- ❌ проверка
Объект.Данные = Неопределеноне сработает, потому что хранилище есть всегда; - ❌ вызов
Получить()у реквизита, который не заполнен, не вызывает ошибку, а даётНеопределено; - ✅ перед обращением к полям полученного значения проверяйте
ТипЗнч, иначе получите «Поле объекта не обнаружено».
Если вы копируете хранилище в цикле по большому справочнику, пустые значения можно пропускать. Так вы не создаёте лишних хранилищ и не раздуваете базу.
Сериализация и выгрузка в файл для обмена
Чтобы передать данные в другую базу, сериализуйте объект. Хранилище значения поддерживает XML-сериализацию, поэтому его можно выгрузить вместе с остальными реквизитами.
Самый короткий путь — записать значение в файл и прочитать обратно:
ЗначениеВФайл("C:\Обмен\данные.val", Данные);
// на другой стороне
Данные = ЗначениеИзФайла("C:\Обмен\данные.val");
Для обмена в формате XML используйте сериализатор:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\Обмен\данные.xml");
ЗаписьXML.ЗаписатьОбъявлениеXML();
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, Объект.Данные);
ЗаписьXML.Закрыть();
Если нужен именно двоичный файл — например, чтобы сохранить вложение из хранилища, — достаньте данные и запишите их:
ДвоичныеДанные = Объект.Данные.Получить();
Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные") Тогда
ДвоичныеДанные.Записать("C:\Обмен\вложение.bin");
КонецЕсли;
Две особенности:
- 📁 путь должен быть доступен тому процессу, который выполняет код. На сервере это каталог сервера, а не папка на вашем компьютере;
- 🔁 при загрузке в другую базу типы внутри хранилища должны существовать и там. Таблица значений с произвольными колонками прочитается везде, а ссылки на справочники — только если такой справочник есть в приёмнике.
Прежде чем массово перезаписывать реквизиты-хранилища в рабочей базе, сделайте копию базы: исходное содержимое после перезаписи не вернуть.
Тонкий клиент, веб-клиент и сервер
Хранилище значения — тип, который удобнее всего обрабатывать на сервере. На клиенте работать с ним можно, но тяжёлые данные гонять туда-сюда не стоит.
Практические правила:
- Распаковку и упаковку большого содержимого выполняйте в серверной процедуре с директивой
&НаСервере. - Если нужно отдать данные клиенту — например, предложить сохранить файл
, — поместите двоичные данные во временное хранилище через
ПоместитьВоВременноеХранилищеи передайте на клиент только адрес. - В веб-клиенте прямая запись в произвольную папку диска недоступна. Файл сохраняют через диалог браузера: сервер готовит двоичные данные, клиент вызывает получение файла по адресу.
- В реквизите формы тип
ХранилищеЗначенияотдельно не отображается. Чтобы показать пользователю содержимое, выводите распакованные данные.
Если вы пишете обработку для копирования между базами, учтите, что в клиент-серверном варианте работа тяжёлая по трафику: каждое обращение Получить() на клиенте тянет данные по сети.
Массовое копирование по справочнику
Когда хранилищ много, копирование делают не вручную, а выборкой. Запрос берёт ссылки на элементы, а хранилище читают уже у объекта, а не в запросе. Так проще и надёжнее: данные хранилища через запрос получить можно, но для обработки их всё равно придётся распаковывать.
Порядок работы такой:
- Выберите ссылки на элементы-источники запросом к справочнику.
- В цикле получайте объект методом
ПолучитьОбъект(). - Копируйте реквизит присваиванием или через
Получить()и новый конструктор. - Записывайте объект и переходите к следующему.
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Источник = Выборка.Ссылка.ПолучитьОбъект();
Приемник = Справочники.Номенклатура.СоздатьЭлемент();
Приемник.Наименование = Источник.Наименование + " (копия)";
Приемник.Данные = Источник.Данные;
Приемник.Записать();
КонецЦикла;
Для пакета в тысячи элементов запускайте код фоновым заданием. Так вы не держите интерактивный сеанс, а при сбое потеряете не всё: записанные элементы останутся.
Если в хранилищах лежат тяжёлые вложения, копируйте порциями, например по 500 элементов, и пишите в журнал, на каком элементе остановились. Повторный запуск продолжит с места остановки.
Типичные ошибки при работе с хранилищем
Большинство сбоев сводится к четырём ситуациям. Сверьтесь со списком, прежде чем искать причину в платформе.
- 🚫 Ожидание
Неопределеноот реквизита. Хранилище есть всегда, пустым бывает только то, что вернулПолучить(). - 🚫 Обращение к свойствам без проверки типа. Если вместо структуры лежит строка или двоичные данные, получите ошибку «Поле объекта не обнаружено».
- 🚫 Попытка сериализовать несовместимое значение. В конструктор нельзя передать значение, которое не поддерживает сериализацию. Платформа выдаст исключение при создании хранилища.
- 🚫 Запись из клиента без сервера. На клиенте конструктор отработает, но потом вы будете передавать большой объём данных по сети и ждать.
Проверка размера и содержимого перед копированием
Прежде чем копировать пакет, оцените, сколько он займёт. Для этого распакуйте одно хранилище и посмотрите на тип и размер:
Значение = Источник.Данные.Получить();
Сообщить("Тип: " + Строка(ТипЗнч(Значение)));
Если ТипЗнч(Значение) = Тип("ДвоичныеДанные") Тогда
Сообщить("Размер, байт: " + Формат(Значение.Размер(), "ЧГ=0"));
КонецЕсли;
Это занимает секунды и спасает от неприятных сюрпризов. Если там таблица значений, выведите число строк через Количество() и сразу видно порядок нагрузки. Умножьте результат на число элементов: это ориентировочный объём, который добавится в базу. Для файловой базы такой запас лучше проверить заранее, для серверной — следите за местом в СУБД.
После копирования сверьте результат: выберите несколько случайных элементов, распакуйте хранилища у источника и приёмника и сравните тип и размер. Совпали — копия прошла корректно. Расхождение в размере у сжатых данных нормально, если вы меняли степень сжатия; различие в типе означает ошибку в алгоритме копирования, и пакет лучше остановить.
Чек-лист перед копированием
Частые вопросы
Можно ли изменить содержимое хранилища, не создавая новое?
Получить(), измените его и упакуйте новым конструктором.Что вернёт Получить() у пустого хранилища?
Неопределено. Проверяйте результат метода, а не само хранилище: оно создаётся платформой всегда.Влияет ли присваивание хранилища на исходный объект?
Как сохранить файл из хранилища на диск?
Получить(). Если это ДвоичныеДанные, вызовите у них метод Записать() с путём. В веб-клиенте файл отдают через временное хранилище.Можно ли положить в хранилище результат запроса?
Выгрузить() и упакуйте таблицу.