Разработка и Конфигурация · 12 мин
Как скопировать хранилище значений в 1С
Разбираем, как скопировать хранилище значений в 1С: через сериализацию, ЗначениеВСтрокуВнутр, запросы между базами. Примеры кода, ошибки и проверка результата.
Хранилище значения (тип ХранилищеЗначения) в 1С — это контейнер, который упаковывает произвольные данные: таблицы значений, картинки, файлы, настройки отчётов. Скопировать его «в лоб» присваиванием нельзя — вернуться будет ссылка на тот же объект, и изменения в одном месте отразятся в другом. Поэтому задача копирования решается через извлечение и повторную упаковку содержимого либо через сериализацию.
На практике сценарии различаются: иногда нужно скопировать значение между реквизитами одного объекта, иногда — перенести данные между разными информационными базами, а иногда — клонировать настройки для сотни элементов справочника. Для каждого случая есть свой рабочий приём, и ниже разобраны все основные варианты с готовым кодом.
Важно понимать ограничения: внутри хранилища могут лежать данные, которые не сериализуются напрямую (например, ссылки на объекты другой базы), а объёмные двоичные данные при неаккуратном копировании заметно нагружают сервер. Учитывайте это при выборе способа.
Как устроено хранилище значений и почему простое присваивание не работает
Тип ХранилищеЗначения — это объект-обёртка. Когда вы пишете НовоеХранилище = СтароеХранилище, копируется не содержимое, а ссылка на тот же объект в памяти. Для реквизитов базы данных ситуация немного иная: при записи объекта содержимое хранилища сериализуется и сохраняется в поле таблицы, поэтому два разных реквизита после записи физически независимы. Но в оперативной памяти, пока объект не записан, оба реквизита могут указывать на один экземпляр.
Корректный базовый приём выглядит так:
ИсходноеЗначение = СтарыйОбъект.Данные.Получить();
НовыйОбъект.Данные = Новый ХранилищеЗначения(ИсходноеЗначение, Новый СжатиеДанных(9));
Метод Получить() извлекает упакованное значение, а конструктор Новый ХранилищеЗначения() создаёт независимый контейнер. Второй параметр — степень сжатия — необязателен, но для объёмных данных (картинки, таблицы) сжатие уменьшает размер поля в базе.
Копирование между реквизитами одного объекта
Самый частый сценарий — продублировать данные внутри одного элемента справочника или документа, например скопировать присоединённый макет из реквизита «Шаблон» в реквизит «ШаблонАрхив». Здесь достаточно извлечь значение и упаковать заново:
Объект = СсылкаНаЭлемент.ПолучитьОбъект();
Содержимое = Объект.Шаблон.Получить();
Если Содержимое <> Неопределено Тогда
Объект.ШаблонАрхив = Новый ХранилищеЗначения(Содержимое);
КонецЕсли;
Объект.Записать();
Проверка на Неопределено обязательна: если хранилище пустое, Получить() вернёт Неопределено, и упаковывать его повторно бессмысленно — вы просто создадите лишний пустой объект. После записи убедитесь, что целевой реквизит заполнен: повторно прочитайте объект и вызовите Получить() для нового поля.
Если копируете между двумя разными объектами одной базы, логика та же — разница только в том, что читаете один объект, а записываете другой.
- 1Получите объект-источник методом ПолучитьОбъект() или прочитайте реквизит через запрос
- 2Извлеките содержимое хранилища методом Получить()
- 3Проверьте, что результат не равен Неопределено
- 4Создайте новый контейнер: Новый ХранилищеЗначения(Содержимое)
- 5Присвойте его целевому реквизиту и запишите объект
- 6Перечитайте объект и проверьте, что новое хранилище возвращает те же данные
Копирование через сериализацию: ЗначениеВСтрокуВнутр
Когда нужно перенести хранилище между разными информационными базами или сохранить его в текстовом виде (например, в файле обмена), используется внутренняя сериализация платформы:
// В базе-источнике
Содержимое = Объект.Данные.Получить();
СтрокаДляПереноса = ЗначениеВСтрокуВнутр(Содержимое);
// В базе-приёмнике
Восстановленное = ЗначениеИзСтрокиВнутр(СтрокаДляПереноса);
НовыйОбъект.Данные = Новый ХранилищеЗначения(Восстановленное);
Функция ЗначениеВСтрокуВнутр преобразует значение в текстовое представление внутреннего формата платформы. Это удобно, но есть ограничение: формат привязан к версии платформы и не гарантирует совместимость между сильно различающимися релизами. Для обмена между базами на разных версиях надёжнее XML-сериализация через ЗаписатьXML/ПрочитатьXML или универсальный формат XDTO.
Учтите, что ссылочные значения внутри сериализованных данных восстановятся корректно только если в базе-приёмнике существуют объекты с теми же GUID. Иначе получите «битые» ссылки. Для переноса между независимыми базами ссылки лучше заменять на представления или синхронизировать по ключевым полям.
Копирование запросом без получения объекта
При массовом копировании (например, заполнить хранилище у тысячи элементов справочника) получение каждого объекта через ПолучитьОбъект() работает медленно. Быстрее прочитать данные запросом, а записывать пакетно. Однако есть нюанс: запрос возвращает хранилище как значение поля, и работать с ним можно стандартно:
Запрос = Новый Запрос("ВЫБРАТЬ Ссылка, Данные ИЗ Справочник.Макеты");
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Содержимое = Выборка.Данные.Получить();
Если Содержимое = Неопределено Тогда
Продолжить;
КонецЕсли;
Объект = Выборка.Ссылка.ПолучитьОбъект();
Объект.ДанныеАрхив = Новый ХранилищеЗначения(Содержимое);
Объект.Записать();
КонецЦикла;
Для очень больших объёмов стоит обрабатывать записи порциями и фиксировать транзакции блоками, чтобы не держать длинную транзакцию и не блокировать таблицу для других пользователей. Также полезно логировать обработанные ссылки — при сбое вы продолжите с места остановки, а не начнёте заново.
При массовом копировании объёмных хранилищ (файлы, картинки) база заметно разрастается: каждое хранилище после записи — это отдельная физическая копия данных. Перед обработкой оцените итоговый прирост размера базы и свободное место на диске СУБД.
Копирование через временный файл
Если внутри хранилища лежит двоичное содержимое (картинка, файл Excel, архив), удобен путь через временный файл — особенно когда нужно ещё и проверить содержимое глазами:
ДвоичныеДанные = Объект.Файл.Получить();
ИмяВременного = ПолучитьИмяВременногоФайла("pdf");
ДвоичныеДанные.Записать(ИмяВременного);
// ... проверка или обработка файла ...
НовыеДвоичные = Новый ДвоичныеДанные(ИмяВременного);
НовыйОбъект.Файл = Новый ХранилищеЗначения(НовыеДвоичные);
УдалитьФайлы(ИмяВременного);
Этот способ незаменим, когда копирование совмещено с обработкой: например, нужно пересохранить картинку в другом формате или подписать PDF перед помещением в новое хранилище. Минус — лишние дисковые операции, поэтому для простого клонирования без обработки прямое копирование через Получить() предпочтительнее.
Выбор способа под задачу
Единого «правильного» метода нет — выбор зависит от того, где находятся источник и приёмник, какой объём данных и нужна ли промежуточная обработка. Ориентируйтесь на сопоставление ниже.
Сравнение способов копирования
Стандарт для копирования внутри одной базы. Быстро и надёжно, но не подходит для переноса между базами
Перенос между базами одной версии платформы через текст. Ограничение: зависимость от версии и проблемы со ссылками
Обмен между базами на разных версиях платформы. Более громоздкий код, зато устойчивый формат
Когда внутри двоичные данные и нужна их проверка или обработка. Медленнее из-за дисковых операций
Как выбрать действие
Проверка результата копирования
После копирования убедитесь, что данные действительно перенеслись и независимы. Минимальная проверка — перечитать целевой объект и извлечь содержимое нового хранилища: оно должно быть не Неопределено и соответствовать исходному по смыслу (для двоичных данных можно сравнить размер или хеш).
Как проверить результат
Возвращает данные, идентичные исходным
Возвращает Неопределено или вызывает ошибку
Изменение исходного хранилища не влияет на копию после записи
Копия меняется вместе с оригиналом — значит, скопирована ссылка
Ссылки разворачиваются в реальные объекты приёмника
Ссылки отображаются как <Объект не найден>
Прирост соответствует ожидаемому объёму копий
Резкий скачок размера — возможно, задвоение данных
Для программной сверки двоичных данных удобно сравнивать хеш-суммы через объект ХешированиеДанных (MD5 или SHA). Если хеши исходного и скопированного содержимого совпадают — копия побайтово идентична.
Частые вопросы
Можно ли скопировать хранилище значения простым присваиванием реквизитов?
Для реквизитов базы данных присваивание вида Объект2.Данные = Объект1.Данные после записи даст физически раздельные копии, поскольку платформа сериализует каждое поле отдельно. Однако в памяти до записи оба реквизита указывают на один объект, что может привести к неочевидным эффектам. Надёжная практика — всегда создавать новый контейнер через Новый ХранилищеЗначения(Значение).
Как скопировать хранилище, если внутри лежит таблица значений?
Точно так же: извлеките таблицу методом Получить() и упакуйте в новое хранилище. Если нужна полностью независимая копия таблицы для дальнейшего изменения, дополнительно вызовите у неё метод Скопировать() перед упаковкой — иначе копия хранилища будет ссылаться на ту же таблицу в памяти.
Почему после переноса в другую базу хранилище не открывается?
Чаще всего причина в несовместимости формата сериализации (разные версии платформы у источника и приёмника) или в битых ссылках внутри содержимого. Проверьте версии платформы обеих баз и попробуйте перенос через XML вместо внутреннего формата. Если внутри были ссылки, убедитесь, что объекты с теми же GUID существуют в приёмнике.
Как скопировать хранилище у всех элементов справочника разом?
Используйте запрос для выборки ссылок и значений хранилища, затем в цикле получайте объект, создавайте новый контейнер и записывайте. Для больших объёмов разбивайте обработку на порции с отдельными транзакциями и ведите журнал обработанных ссылок, чтобы при сбое продолжить с места остановки.
Влияет ли сжатие данных на копирование?
Степень сжатия задаётся только при создании нового хранилища и на само копирование не влияет — содержимое переносится одинаково. Сжатие влияет лишь на размер поля в базе: для уже сжатых данных (архивы, JPEG) эффект минимален, для таблиц и текстов — заметный.
Можно ли скопировать хранилище без программирования?
Штатных пользовательских средств для копирования хранилища значений между реквизитами нет — это задача уровня разработчика. Для переноса между базами можно воспользоваться типовой обработкой «Выгрузка и загрузка данных XML», если она доступна в вашей конфигурации: она корректно переносит реквизиты типа ХранилищеЗначения вместе с объектами.
Что важно запомнить
Копирование хранилища значения в 1С всегда сводится к извлечению содержимого методом Получить() и упаковке в новый контейнер. Для переноса между базами применяйте сериализацию, следите за ссылками внутри данных и проверяйте результат после записи. Перед массовыми операциями делайте резервную копию базы.