Разработка и Конфигурация · 13 мин
1С: как скопировать хранилище значения
Как скопировать хранилище значения в 1С: методы Получить(), Поместить(), работа с двоичными данными и картинками, типичные ошибки и примеры кода.
Хранилище значения в 1С — специальный тип данных, который позволяет сохранять в реквизитах объектов практически любую информацию: двоичные данные, картинки, таблицы значений, сериализованные объекты. Разработчики регулярно сталкиваются с задачей скопировать содержимое хранилища из одного объекта в другой — например, при копировании товара вместе с изображением или при переносе настроек между элементами справочника.
Ключевая особенность типа «ХранилищеЗначения» — это не сами данные, а контейнер-обёртка. Прямое присвоение одного реквизита другому часто приводит к неожиданным результатам: либо копируется ссылка на один и тот же объект в памяти, либо данные теряются при сериализации. Поэтому важно понимать, как именно извлекать значение из хранилища и помещать его в новое хранилище.
Ниже разобраны рабочие способы копирования хранилища значения: программное копирование через встроенный язык, перенос между базами, особенности работы с картинками и двоичными данными, а также типичные ошибки, из-за которых скопированное значение оказывается пустым или повреждённым.
Что такое хранилище значения и почему его нельзя скопировать «в лоб»
Хранилище значения (ValueStorage) — объект встроенного языка 1С, который упаковывает произвольное значение для сохранения в базе данных. В реквизите справочника или документа с типом «ХранилищеЗначения» физически хранится сериализованное представление данных, а не сами данные в исходном виде.
Из-за этого простое присваивание вида НовыйОбъект.Картинка = СтарыйОбъект.Картинка; формально сработает, но таит подводные камни. В пределах одной информационной базы платформа обычно корректно переносит содержимое, однако при передаче между сеансами, через COM-соединение или при обмене данными хранилище может оказаться пустым, потому что сериализация объекта-контейнера отличается от сериализации его содержимого.
Надёжный подход — всегда работать через пару методов: Получить() извлекает значение из хранилища, а конструктор Новый ХранилищеЗначения() упаковывает его заново. Так вы гарантированно создаёте независимую копию данных, а не ссылку на существующий контейнер.
Базовый способ: копирование через Получить() и новый конструктор
Стандартный сценарий — скопировать хранилище из одного элемента справочника в другой в рамках одной базы. Код выглядит так:
ЗначениеИзИсточника = Источник.ФайлДанных.Получить();
Если ЗначениеИзИсточника <> Неопределено Тогда
Приемник.ФайлДанных = Новый ХранилищеЗначения(ЗначениеИзИсточника);
КонецЕсли;
Приемник.Записать();
Обратите внимание на проверку на Неопределено. Если хранилище в источнике пустое, метод Получить() вернёт именно Неопределено, и попытка упаковать это значение без проверки не даст ошибки, но создаст пустое хранилище — что не всегда является желаемым поведением. Явная проверка позволяет отличить «пустое хранилище» от «хранилища нет вообще».
Если нужно скопировать данные с защитой от одновременного изменения, дополнительно можно использовать сжатие: конструктор принимает вторым параметром степень сжатия данных, например Новый ХранилищеЗначения(Значение, Новый СжатиеДанных(9)). Это полезно для больших двоичных данных — файлов и изображений высокого разрешения.
- 1Прочитайте объект-источник и получите содержимое: Источник.Реквизит.Получить()
- 2Проверьте, что результат не равен Неопределено
- 3Создайте новое хранилище: Новый ХранилищеЗначения(Значение)
- 4Присвойте его целевому реквизиту объекта-приёмника
- 5Запишите объект-приёмник и проверьте результат повторным Получить()
Копирование картинок и двоичных данных
Самый частый прикладной сценарий — перенос изображений товаров или прикреплённых файлов. Здесь внутри хранилища обычно лежит объект типа ДвоичныеДанные или Картинка, и у каждого варианта есть нюансы.
Если в хранилище лежат двоичные данные
Двоичные данные копируются напрямую — они уже являются самостоятельным значением, не привязанным к контексту объекта:
ДвоичныеДанные = Источник.ХранилищеФайла.Получить();
Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные") Тогда
Приемник.ХранилищеФайла = Новый ХранилищеЗначения(ДвоичныеДанные);
КонецЕсли;
Проверка типа через ТипЗнч() защищает от ситуации, когда в хранилище по историческим причинам записано что-то другое — например, строка с путём к файлу. Такое бывает в старых базах, где структуру реквизитов меняли несколько раз.
Если в хранилище лежит объект «Картинка»
Объект типа Картинка тоже сериализуется, но при интенсивной работе надёжнее переносить его через двоичные данные. Это гарантирует, что не потеряются параметры формата и не возникнет проблем с несовместимостью видов картинки:
КартинкаИсточника = Источник.Изображение.Получить();
Если ТипЗнч(КартинкаИсточника) = Тип("Картинка") Тогда
ДвоичныеДанные = КартинкаИсточника.ПолучитьДвоичныеДанные();
Приемник.Изображение = Новый ХранилищеЗначения(Новый Картинка(ДвоичныеДанные));
КонецЕсли;
Копирование хранилища между разными базами
Перенос хранилища значения между информационными базами — отдельная задача. Прямое присваивание здесь невозможно в принципе, потому что объект ХранилищеЗначения не переживает границу соединения в исходном виде. Данные нужно извлечь, сериализовать и собрать заново на стороне приёмника.
Через COM-соединение
При работе через COM-коннектор хранилище из внешней базы нельзя просто присвоить локальному реквизиту. Рабочий приём — получить двоичные данные на стороне источника и передать их как значение:
// Код выполняется в контексте COM-соединения
Значение = ВнешнийОбъект.ФайлДанных.Получить();
// Значение передаётся в локальную базу как ДвоичныеДанные
ЛокальныйОбъект.ФайлДанных = Новый ХранилищеЗначения(Значение);
Учтите, что объёмные данные через COM передаются заметно медленнее, чем читаются локально. Для массового переноса файлов лучше использовать промежуточное хранение на диске или передачу через Base64-строку.
Через файл или строку Base64
Универсальный способ, не зависящий от способа соединения: выгрузить двоичные данные во временный файл или преобразовать в Base64-строку, передать, а на приёмной стороне восстановить:
// Источник
ДвоичныеДанные = Источник.ХранилищеФайла.Получить();
СтрокаBase64 = Base64Строка(ДвоичныеДанные);
// Приёмник
ДвоичныеДанные = Base64Значение(СтрокаBase64);
Приемник.ХранилищеФайла = Новый ХранилищеЗначения(ДвоичныеДанные);
Base64 увеличивает объём данных примерно на треть, поэтому для файлов большого размера предпочтительнее передача через файл. Строковый вариант удобен для веб-сервисов и HTTP-обменов, где бинарный формат недоступен.
Способы переноса хранилища между базами
Подходит для прямого обмена между базами на одной платформе. Ограничение: медленная передача больших данных и зависимость от разрядности платформы.
Универсально для HTTP-сервисов и текстовых форматов обмена. Ограничение: рост объёма данных примерно на треть.
Оптимально для больших файлов и пакетной обработки. Ограничение: нужен доступ к общему каталогу и очистка временных файлов.
Копирование хранилища в табличных частях и регистрах
Хранилище значения встречается не только в шапках объектов, но и в табличных частях, а также в измерениях и ресурсах регистров сведений. Логика копирования та же, но появляется цикл по строкам и необходимость аккуратно обрабатывать каждую запись.
Для Каждого СтрокаИсточника Из Источник.Файлы Цикл
НоваяСтрока = Приемник.Файлы.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника, , "ДанныеФайла");
Значение = СтрокаИсточника.ДанныеФайла.Получить();
Если Значение <> Неопределено Тогда
НоваяСтрока.ДанныеФайла = Новый ХранилищеЗначения(Значение);
КонецЕсли;
КонецЦикла;
Обратите внимание на четвёртый параметр ЗаполнитьЗначенияСвойств — список исключений. Реквизит с хранилищем исключается из автоматического копирования и обрабатывается отдельно. Это и надёжнее, и предсказуемее: вы явно контролируете, как именно переносится содержимое.
Для регистров сведений механизм аналогичен: создаётся новая запись через менеджер записи или набор записей, значение извлекается из исходной записи методом Получить() и упаковывается в новое хранилище.
Проверка результата и отладка
После копирования стоит убедиться, что данные действительно перенесены и не повреждены. Минимальная проверка — повторно получить значение из приёмника и сравнить с источником. Для двоичных данных удобно сравнивать размер, для картинок — размер и формат.
КонтрольИсточник = Источник.ХранилищеФайла.Получить();
КонтрольПриемник = Приемник.ХранилищеФайла.Получить();
Если ТипЗнч(КонтрольИсточник) = Тип("ДвоичныеДанные")
И ТипЗнч(КонтрольПриемник) = Тип("ДвоичныеДанные") Тогда
Совпадают = (КонтрольИсточник.Размер() = КонтрольПриемник.Размер());
КонецЕсли;
Сравнение по размеру не гарантирует побайтовой идентичности, но для практических целей его обычно достаточно в сочетании с визуальной проверкой картинки или открытием файла. Если требуется строгая проверка, можно вычислить хеш-сумму обоих значений через объект ХешированиеДанных.
При массовом копировании полезно вести журнал: какие объекты обработаны, где хранилище было пустым, где возникли ошибки. Это позволяет повторить операцию только для проблемных записей, а не для всего массива данных.
Как проверить результат копирования
Получить() возвращает данные того же типа, что и в источнике
Возвращается Неопределено или значение другого типа
Размеры в источнике и приёмнике совпадают
Размер приёмника нулевой или существенно отличается
Изображение отображается, файл открывается без ошибок
Платформа сообщает о повреждении данных или неверном формате
Ограничения и меры предосторожности
Хранилище значения сериализует содержимое целиком, поэтому при массовом копировании больших файлов возможен заметный рост потребления памяти. Если обрабатываются тысячи объектов с файлами по несколько мегабайт, разбивайте операцию на порции и по возможности освобождайте переменные после записи каждой порции.
Не все типы данных сериализуются в хранилище. Например, нельзя поместить в хранилище объекты, привязанные к контексту текущей базы, — сами по себе ссылки сериализуются, но при переносе в другую базу они потеряют смысл, потому что уникальные идентификаторы не совпадут. Перед копированием между базами проверяйте, что внутри хранилища нет ссылочных данных.
Перед массовым копированием в рабочей базе сделайте резервную копию или отработайте операцию на тестовой копии. Ошибка в цикле обработки может затронуть сотни объектов, и откатить такие изменения вручную будет трудоёмко.
Частые вопросы
Можно ли скопировать хранилище значения простым присваиванием?
В пределах одной базы присваивание вида Приемник.Реквизит = Источник.Реквизит обычно срабатывает, но создаёт зависимость от внутренней реализации платформы. Надёжный и переносимый вариант — извлечь значение методом Получить() и создать новое хранилище конструктором. Это особенно важно при обмене между базами и при работе через COM.
Почему после копирования хранилище в приёмнике пустое?
Чаще всего причина в том, что в источнике хранилище уже было пустым, и Получить() вернул Неопределено. Вторая распространённая причина — попытка передать сам объект ХранилищеЗначения через COM или сериализацию, где он не сохраняется. Проверяйте содержимое источника перед копированием и передавайте только извлечённое значение.
Как скопировать хранилище, если неизвестно, что внутри?
Используйте ТипЗнч() после Получить() и обрабатывайте основные варианты: ДвоичныеДанные, Картинка, Строка, ТаблицаЗначений. Для двоичных данных достаточно прямой упаковки, для картинки надёжнее пересоздание через двоичные данные, для таблиц значений — сериализация через ЗначениеВСтрокуВнутр при межбазовом переносе невозможна, поэтому такие структуры переносят через собственные форматы.
Можно ли скопировать хранилище между разными версиями платформы 1С?
Да, если переносить не сам объект хранилища, а его содержимое в нейтральном виде — двоичные данные, файл или Base64-строку. Формат внутренней сериализации объекта ХранилищеЗначения может отличаться между версиями, а вот двоичные данные и стандартные файловые форматы переносятся без проблем.
Как скопировать картинку из хранилища на диск?
Извлеките значение методом Получить(), затем, если это объект Картинка, получите двоичные данные методом ПолучитьДвоичныеДанные() и запишите их методом Записать(ИмяФайла). Если в хранилище уже лежат ДвоичныеДанные, вызовите Записать() сразу у них. Расширение файла выбирайте по формату картинки, который можно получить через свойство Формат().
Влияет ли сжатие данных на копирование хранилища?
Сжатие влияет только на то, как данные хранятся в базе, но не на логику копирования. Метод Получить() автоматически распаковывает содержимое. Если хотите, чтобы копия тоже хранилась в сжатом виде, передайте объект СжатиеДанных вторым параметром конструктора Новый ХранилищеЗначения().
Что важно запомнить
Копирование хранилища значения в 1С выполняется через пару операций: Получить() для извлечения содержимого и Новый ХранилищеЗначения() для упаковки в приёмнике. Проверяйте результат на Неопределено и тип содержимого, при межбазовом переносе используйте двоичные данные, файлы или Base64, а перед массовой обработкой обязательно делайте резервную копию.