При разработке механизмов загрузки внешних файлов (например, макетов MS Word) в базу данных «1С:Предприятие 8.3» программисты часто сталкиваются с ситуацией, когда данные вроде бы успешно помещаются в реквизит типа ХранилищеЗначения, но при попытке их извлечения возникает ошибка или возвращается пустое значение. Рассмотрим эту проблему подробно на примере двух популярных способов работы с файлами и выясним, почему один из них может оказаться «неудачным».
Рассмотрим стандартную задачу: на клиенте выбирается файл, передается на сервер и записывается в справочник. В коде автора используются две процедуры. Первая пытается работать через временное хранилище, а вторая — напрямую через создание объекта ДвоичныеДанные на клиенте. Проанализируем эти подходы.
Способ №1: Помещение через временное хранилище
В этом сценарии используется метод ПоместитьФайл, который возвращает адрес во временном хранилище. Затем на сервере выполняется следующий код:
&НаСервере
Процедура ЗаписатьФайлВрХр(АдресВоВременномХранилище, ИмяФайла)
НовыйФайл = Справочники.ФайлыWord.СоздатьЭлемент();
НовыйФайл.Файл = Новый ХранилищеЗначения(ПолучитьИзВременногоХранилища(АдресВоВременномХранилище));
НовыйФайл.Наименование = ИмяФайла;
НовыйФайл.Записать();
Объект.ФайлWordВ1с = НовыйФайл.Ссылка;
КонецПроцедуры
Способ №2: Помещение через объект ДвоичныеДанные
Здесь файл сразу преобразуется в объект на стороне клиента и передается на сервер как параметр:
&НаСервере
Процедура ЗаписатьФайлДвД(ИмяФайла, Данные)
НовыйФайл = Справочники.ФайлыWord.СоздатьЭлемент();
НовыйФайл.Файл = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных());
НовыйФайл.Наименование = ИмяФайла;
НовыйФайл.Записать();
Объект.ФайлWordВ1с = НовыйФайл.Ссылка;
КонецПроцедуры
Проанализируем ситуацию: почему при использовании временного хранилища данные могут «исчезать»? Основная причина кроется в жизненном цикле временного хранилища. В 1С данные во временном хранилище живут только до тех пор, пока существует контекст, их породивший. Если при вызове метода ПоместитьФайл (или его современных аналогов) не указан УникальныйИдентификатор формы, данные во временном хранилище будут удалены сразу после завершения текущего серверного вызова.
Рассмотрим подробнее ошибки в реализации первого способа:
ПолучитьИзВременногоХранилища на сервере данные могут быть уже стерты сборщиком мусора, если процедура вызывается в другом контексте.ХранилищаЗначения важно, чтобы во временном хранилище находились именно ДвоичныеДанные. Если по адресу находится что-то иное (например, ссылка или строка), то метод Получить() в дальнейшем может вести себя непредсказуемо.Знч = Объект.ФайлWordВ1с.Файл.Получить() на клиенте, платформа пытается выполнить десериализацию. Если данные в хранилище были помещены некорректно, Знч примет значение Неопределено, и вызов Знч.Записать() вызовет фатальную ошибку.Чтобы первый способ работал стабильно, необходимо гарантировать сохранность данных. Разберем по шагам, как это сделать правильно. При вызове функций помещения файлов обязательно передавайте идентификатор формы (ЭтаФорма.УникальныйИдентификатор). Это «привяжет» данные к жизни формы, и они не исчезнут преждевременно.
Также проанализируем современные требования платформы. Начиная с версии 8.3.15, метод ПоместитьФайл считается устаревшим. Рассмотрим более надежный асинхронный метод:
&НаКлиенте
Процедура ЗагрузитьФайлНадежно(Команда)
Описание = Новый ОписаниеПередаваемогоФайла(ПолноеИмяФайла);
НачатьПомещениеФайлаНаСервер(,, Описание, ЭтаФорма.УникальныйИдентификатор);
КонецПроцедуры
Использование ЭтаФорма.УникальныйИдентификатор — это критически важный момент. Именно он гарантирует, что на сервере метод ПолучитьИзВременногоХранилища(Адрес) вернет реальный объект ДвоичныеДанные, а не пустоту.
Теперь посмотрим на пример извлечения данных. В исходном коде автора процедура ПолучитьДанные(Каталог) выполняется на сервере. Это правильное решение, так как работа с ХранилищемЗначения наиболее стабильна именно в серверном контексте.
Однако, проанализируем способ записи файла на диск. Попытка вызвать метод Записать() у объекта, полученного из хранилища, напрямую на клиенте — это риск. Правильнее будет следующая схема:
ДвоичныеДанные из ХранилищаЗначения.ПолучитьФайл() для сохранения данных в файловую систему пользователя.Рассмотрим пример кода для безопасного извлечения:
&НаСервере
Функция ПолучитьФайлВоВременноеХранилище()
// Извлекаем объект из базы
ДанныеХранилища = Объект.ФайлWordВ1с.Файл.Получить();
Если ТипЗнч(ДанныеХранилища) = Тип("ДвоичныеДанные") Тогда
Возврат ПоместитьВоВременноеХранилище(ДанныеХранилища, ЭтаФорма.УникальныйИдентификатор);
КонецЕсли;
Возврат "";
КонецФункции
&НаКлиенте
Процедура ВыгрузитьФайл(Команда)
Адрес = ПолучитьФайлВоВременноеХранилище();
Если Не ПустаяСтрока(Адрес) Тогда
ПолучитьФайл(Адрес, "ИмяНужногоФайла.docx", Истина);
КонецЕсли;
КонецПроцедуры
В способе №2 автор использует Новый ХранилищеЗначения(Данные, Новый СжатиеДанных()). Важно понимать, что платформа по умолчанию сжимает данные в хранилище (обычно это уровень сжатия 6). Для файлов форматов .docx, .xlsx или .zip, которые уже являются сжатыми архивами, дополнительное сжатие в 1С практически не дает эффекта по объему, но потребляет ресурсы процессора. Рекомендуем использовать явное сжатие только для текстовых документов (.txt, .xml, .json) или больших логов.
Подведем итоги нашего анализа. Способ помещения данных через ДвоичныеДанные (второй способ автора) работал лучше, потому что он миновал этап неопределенности временного хранилища. Первый способ («через адрес») давал сбой из-за того, что данные удалялись из памяти сервера раньше, чем их успевали записать в справочник.
Основные правила работы с ХранилищемЗначения:
Получить() может вернуть Неопределено, поэтому всегда делайте проверку типа полученного значения перед использованием.