К статье
Практический материал

Разработка и Конфигурация · 13 мин

1С: как скопировать хранилище значения

Как скопировать хранилище значения в 1С: методы Получить(), Поместить(), работа с двоичными данными и картинками, типичные ошибки и примеры кода.

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

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

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

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

Хранилище значения (ValueStorage) — объект встроенного языка 1С, который упаковывает произвольное значение для сохранения в базе данных. В реквизите справочника или документа с типом «ХранилищеЗначения» физически хранится сериализованное представление данных, а не сами данные в исходном виде.

Из-за этого простое присваивание вида НовыйОбъект.Картинка = СтарыйОбъект.Картинка; формально сработает, но таит подводные камни. В пределах одной информационной базы платформа обычно корректно переносит содержимое, однако при передаче между сеансами, через COM-соединение или при обмене данными хранилище может оказаться пустым, потому что сериализация объекта-контейнера отличается от сериализации его содержимого.

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

Базовый способ: копирование через Получить() и новый конструктор

Стандартный сценарий — скопировать хранилище из одного элемента справочника в другой в рамках одной базы. Код выглядит так:

ЗначениеИзИсточника = Источник.ФайлДанных.Получить();

Если ЗначениеИзИсточника <> Неопределено Тогда

Приемник.ФайлДанных = Новый ХранилищеЗначения(ЗначениеИзИсточника);

КонецЕсли;

Приемник.Записать();

Обратите внимание на проверку на Неопределено. Если хранилище в источнике пустое, метод Получить() вернёт именно Неопределено, и попытка упаковать это значение без проверки не даст ошибки, но создаст пустое хранилище — что не всегда является желаемым поведением. Явная проверка позволяет отличить «пустое хранилище» от «хранилища нет вообще».

Если нужно скопировать данные с защитой от одновременного изменения, дополнительно можно использовать сжатие: конструктор принимает вторым параметром степень сжатия данных, например Новый ХранилищеЗначения(Значение, Новый СжатиеДанных(9)). Это полезно для больших двоичных данных — файлов и изображений высокого разрешения.

Порядок копирования хранилища значения
  1. 1
    Прочитайте объект-источник и получите содержимое: Источник.Реквизит.Получить()
  2. 2
    Проверьте, что результат не равен Неопределено
  3. 3
    Создайте новое хранилище: Новый ХранилищеЗначения(Значение)
  4. 4
    Присвойте его целевому реквизиту объекта-приёмника
  5. 5
    Запишите объект-приёмник и проверьте результат повторным Получить()

Копирование картинок и двоичных данных

Самый частый прикладной сценарий — перенос изображений товаров или прикреплённых файлов. Здесь внутри хранилища обычно лежит объект типа ДвоичныеДанные или Картинка, и у каждого варианта есть нюансы.

Если в хранилище лежат двоичные данные

Двоичные данные копируются напрямую — они уже являются самостоятельным значением, не привязанным к контексту объекта:

ДвоичныеДанные = Источник.ХранилищеФайла.Получить();

Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные") Тогда

Приемник.ХранилищеФайла = Новый ХранилищеЗначения(ДвоичныеДанные);

КонецЕсли;

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

Если в хранилище лежит объект «Картинка»

Объект типа Картинка тоже сериализуется, но при интенсивной работе надёжнее переносить его через двоичные данные. Это гарантирует, что не потеряются параметры формата и не возникнет проблем с несовместимостью видов картинки:

КартинкаИсточника = Источник.Изображение.Получить();

Если ТипЗнч(КартинкаИсточника) = Тип("Картинка") Тогда

ДвоичныеДанные = КартинкаИсточника.ПолучитьДвоичныеДанные();

Приемник.Изображение = Новый ХранилищеЗначения(Новый Картинка(ДвоичныеДанные));

КонецЕсли;

Копирование хранилища между разными базами

Перенос хранилища значения между информационными базами — отдельная задача. Прямое присваивание здесь невозможно в принципе, потому что объект ХранилищеЗначения не переживает границу соединения в исходном виде. Данные нужно извлечь, сериализовать и собрать заново на стороне приёмника.

Через COM-соединение

При работе через COM-коннектор хранилище из внешней базы нельзя просто присвоить локальному реквизиту. Рабочий приём — получить двоичные данные на стороне источника и передать их как значение:

// Код выполняется в контексте COM-соединения

Значение = ВнешнийОбъект.ФайлДанных.Получить();

// Значение передаётся в локальную базу как ДвоичныеДанные

ЛокальныйОбъект.ФайлДанных = Новый ХранилищеЗначения(Значение);

Учтите, что объёмные данные через COM передаются заметно медленнее, чем читаются локально. Для массового переноса файлов лучше использовать промежуточное хранение на диске или передачу через Base64-строку.

Через файл или строку Base64

Универсальный способ, не зависящий от способа соединения: выгрузить двоичные данные во временный файл или преобразовать в Base64-строку, передать, а на приёмной стороне восстановить:

// Источник

ДвоичныеДанные = Источник.ХранилищеФайла.Получить();

СтрокаBase64 = Base64Строка(ДвоичныеДанные);

// Приёмник

ДвоичныеДанные = Base64Значение(СтрокаBase64);

Приемник.ХранилищеФайла = Новый ХранилищеЗначения(ДвоичныеДанные);

Base64 увеличивает объём данных примерно на треть, поэтому для файлов большого размера предпочтительнее передача через файл. Строковый вариант удобен для веб-сервисов и HTTP-обменов, где бинарный формат недоступен.

Способы переноса хранилища между базами

COM-соединение

Подходит для прямого обмена между базами на одной платформе. Ограничение: медленная передача больших данных и зависимость от разрядности платформы.

Base64-строка

Универсально для HTTP-сервисов и текстовых форматов обмена. Ограничение: рост объёма данных примерно на треть.

Временный файл

Оптимально для больших файлов и пакетной обработки. Ограничение: нужен доступ к общему каталогу и очистка временных файлов.

Копирование хранилища в табличных частях и регистрах

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

Для Каждого СтрокаИсточника Из Источник.Файлы Цикл

НоваяСтрока = Приемник.Файлы.Добавить();

ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника, , "ДанныеФайла");

Значение = СтрокаИсточника.ДанныеФайла.Получить();

Если Значение <> Неопределено Тогда

НоваяСтрока.ДанныеФайла = Новый ХранилищеЗначения(Значение);

КонецЕсли;

КонецЦикла;

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

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

Типичные ошибки при копировании хранилища
01
Прямое присвоение реквизитов без Получить() — при обмене между базами хранилище приходит пустым
02
Отсутствие проверки на Неопределено — пустое хранилище источника молча превращается в пустое хранилище приёмника, и отличить ошибку от исходной пустоты невозможно
03
Копирование через ЗаполнитьЗначенияСвойств без исключения реквизита-хранилища — поведение зависит от контекста и сложно отлаживается
04
Игнорирование типа содержимого — в хранилище может лежать строка вместо двоичных данных, и код падает на этапе использования, а не копирования
05
Забытый Записать() у объекта-приёмника — копирование выполнено в памяти, но не сохранено в базе

Проверка результата и отладка

После копирования стоит убедиться, что данные действительно перенесены и не повреждены. Минимальная проверка — повторно получить значение из приёмника и сравнить с источником. Для двоичных данных удобно сравнивать размер, для картинок — размер и формат.

КонтрольИсточник = Источник.ХранилищеФайла.Получить();

КонтрольПриемник = Приемник.ХранилищеФайла.Получить();

Если ТипЗнч(КонтрольИсточник) = Тип("ДвоичныеДанные")

И ТипЗнч(КонтрольПриемник) = Тип("ДвоичныеДанные") Тогда

Совпадают = (КонтрольИсточник.Размер() = КонтрольПриемник.Размер());

КонецЕсли;

Сравнение по размеру не гарантирует побайтовой идентичности, но для практических целей его обычно достаточно в сочетании с визуальной проверкой картинки или открытием файла. Если требуется строгая проверка, можно вычислить хеш-сумму обоих значений через объект ХешированиеДанных.

При массовом копировании полезно вести журнал: какие объекты обработаны, где хранилище было пустым, где возникли ошибки. Это позволяет повторить операцию только для проблемных записей, а не для всего массива данных.

Контрольные точки

Как проверить результат копирования

Значение в приёмнике
Норма

Получить() возвращает данные того же типа, что и в источнике

Тревожный признак

Возвращается Неопределено или значение другого типа

Размер двоичных данных
Норма

Размеры в источнике и приёмнике совпадают

Тревожный признак

Размер приёмника нулевой или существенно отличается

Открытие картинки или файла
Норма

Изображение отображается, файл открывается без ошибок

Тревожный признак

Платформа сообщает о повреждении данных или неверном формате

Ограничения и меры предосторожности

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

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

Перед массовым копированием в рабочей базе сделайте резервную копию или отработайте операцию на тестовой копии. Ошибка в цикле обработки может затронуть сотни объектов, и откатить такие изменения вручную будет трудоёмко.

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

Можно ли скопировать хранилище значения простым присваиванием?

В пределах одной базы присваивание вида Приемник.Реквизит = Источник.Реквизит обычно срабатывает, но создаёт зависимость от внутренней реализации платформы. Надёжный и переносимый вариант — извлечь значение методом Получить() и создать новое хранилище конструктором. Это особенно важно при обмене между базами и при работе через COM.

Почему после копирования хранилище в приёмнике пустое?

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

Как скопировать хранилище, если неизвестно, что внутри?

Используйте ТипЗнч() после Получить() и обрабатывайте основные варианты: ДвоичныеДанные, Картинка, Строка, ТаблицаЗначений. Для двоичных данных достаточно прямой упаковки, для картинки надёжнее пересоздание через двоичные данные, для таблиц значений — сериализация через ЗначениеВСтрокуВнутр при межбазовом переносе невозможна, поэтому такие структуры переносят через собственные форматы.

Можно ли скопировать хранилище между разными версиями платформы 1С?

Да, если переносить не сам объект хранилища, а его содержимое в нейтральном виде — двоичные данные, файл или Base64-строку. Формат внутренней сериализации объекта ХранилищеЗначения может отличаться между версиями, а вот двоичные данные и стандартные файловые форматы переносятся без проблем.

Как скопировать картинку из хранилища на диск?

Извлеките значение методом Получить(), затем, если это объект Картинка, получите двоичные данные методом ПолучитьДвоичныеДанные() и запишите их методом Записать(ИмяФайла). Если в хранилище уже лежат ДвоичныеДанные, вызовите Записать() сразу у них. Расширение файла выбирайте по формату картинки, который можно получить через свойство Формат().

Влияет ли сжатие данных на копирование хранилища?

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

Итог

Что важно запомнить

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