При разработке функционала прикрепления файлов в управляемых формах 1С часто возникает необходимость хранить бинарные данные в регистре сведений и затем выгружать или открывать их на стороне клиента. Однако разработчики регулярно сталкиваются с ошибками вида «Неправильный путь к файлу», «Файл не обнаружен» или «Ошибка удаления значения из временного хранилища».
Разберем по шагам, почему возникают эти ошибки, как устроена адресация данных в платформе 1С, как корректно сформировать КлючЗаписи регистра сведений и получить прямую навигационную ссылку на файл без лишней нагрузки на сервер.
Проанализируем две основные проблемы, которые приводят к сбоям в коде:
Ссылка, как у справочника или документа. Единственным идентификатором конкретной строки в регистре является совокупность всех его измерений (а для периодических регистров — еще и период). Если при создании объекта КлючЗаписи пропустить хотя бы одно измерение, платформа не сможет однозначно определить строку и вернет ссылку на неопределенный набор, из-за чего навигационная ссылка на реквизит не сформируется или будет указывать на несуществующую запись.e1cib/tempstorage/... и указывает на оперативную память сеанса или кластера серверов. Навигационная ссылка на ресурс регистра выглядит как e1cib/data/РегистрСведений... и указывает непосредственно на поле в таблице базы данных. Попытка вызвать метод УдалитьИзВременногоХранилища() для адреса базы данных гарантированно вызывает исключение.Рассмотрим процесс помещения файла в базу данных. Сначала на клиенте файл помещается во временное хранилище, а затем на сервере считывается и упаковывается в ХранилищеЗначения для сохранения в ресурсе регистра сведений.
Посмотрим на пример кода записи через МенеджерЗаписи:
&НаСервере
Процедура СохранитьФайлВРегистр(ВладелецФайла, ИмяФайла, НомерФайла, АдресВХ)
Если ЭтоАдресВременногоХранилища(АдресВХ) Тогда
// Получаем бинарные данные из временного хранилища
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХ);
// Создаем менеджер записи и заполняем ВСЕ измерения
МенеджерЗаписи = РегистрыСведений.ХранилищеФайлов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ВладелецФайла = ВладелецФайла;
МенеджерЗаписи.ИмяФайла = ИмяФайла;
МенеджерЗаписи.НомерФайла = НомерФайла;
// Записываем файл в ресурс с типом ХранилищеЗначения (со сжатием)
МенеджерЗаписи.ДвоичныеДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных());
МенеджерЗаписи.Записать(Истина);
КонецЕсли;
КонецПроцедуры
Для выгрузки файла на клиент вовсе не обязательно считывать тяжелые бинарные данные в память сервера через запрос или менеджер записи и снова вызывать ПоместитьВоВременноеХранилище(). Платформа 1С позволяет напрямую получить навигационную ссылку на ресурс записи. Клиентская часть платформы умеет самостоятельно выгружать такие данные в файл потоком.
Главное правило: в структуру для создания ключа записи должны входить абсолютно все измерения регистра.
Разберем серверную функцию формирования ссылки:
&НаСервере
Функция ПолучитьСсылкуНаФайлВРегистре(ВладелецФайла, ИмяФайла, НомерФайла)
Отбор = Новый Структура;
Отбор.Вставить("ВладелецФайла", ВладелецФайла);
Отбор.Вставить("ИмяФайла", СокрЛП(ИмяФайла));
Отбор.Вставить("НомерФайла", НомерФайла);
// Создаем ключ записи со ВСЕМИ измерениями регистра
КлючЗаписиРег = РегистрыСведений.ХранилищеФайлов.СоздатьКлючЗаписи(Отбор);
// Получаем навигационную ссылку непосредственно на ресурс с типом ХранилищеЗначения
НавигационнаяСсылка = ПолучитьНавигационнуюСсылку(КлючЗаписиРег, "ДвоичныеДанныеФайла");
Возврат НавигационнаяСсылка;
КонецФункции
Теперь на клиенте вызываем получение файла. Обратите внимание: метод УдалитьИзВременногоХранилища() вызывать не нужно, так как навигационная ссылка ведет на постоянные данные информационной базы:
&НаКлиенте
Процедура ВыгрузитьФайлНаКлиенте(ВладелецФайла, ИмяФайла, НомерФайла)
СсылкаНаДанные = ПолучитьСсылкуНаФайлВРегистре(ВладелецФайла, ИмяФайла, НомерФайла);
Если ЗначениеЗаполнено(СсылкаНаДанные) Тогда
// Запускаем интерактивное сохранение файла пользователю
ПолучитьФайл(СсылкаНаДанные, ИмяФайла, Истина);
КонецЕсли;
КонецПроцедуры
Если по какой-либо причине получение навигационной ссылки на ключ записи не подходит (например, структура измерений динамическая или данные файла перед отдачей модифицируются на сервере), файл можно извлечь и передать через временное хранилище.
Посмотрим на пример извлечения через запрос:
&НаСервере
Функция ПоместитьФайлВХранилище(ВладелецФайла, ИмяФайла, НомерФайла)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХранилищеФайлов.ДвоичныеДанныеФайла КАК ДвоичныеДанныеФайла
|ИЗ
| РегистрСведений.ХранилищеФайлов КАК ХранилищеФайлов
|ГДЕ
| ХранилищеФайлов.ВладелецФайла = &ВладелецФайла
| И ХранилищеФайлов.ИмяФайла = &ИмяФайла
| И ХранилищеФайлов.НомерФайла = &НомерФайла";
Запрос.УстановитьПараметр("ВладелецФайла", ВладелецФайла);
Запрос.УстановитьПараметр("ИмяФайла", СокрЛП(ИмяФайла));
Запрос.УстановитьПараметр("НомерФайла", НомерФайла);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Хранилище = Выборка.ДвоичныеДанныеФайла;
Если ЗначениеЗаполнено(Хранилище) Тогда
ДвоичныеДанные = Хранилище.Получить();
// Помещаем во временное хранилище с привязкой к форме
Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
КонецЕсли;
КонецЕсли;
Возврат "";
КонецФункции
При использовании этого варианта адрес действительно будет иметь формат e1cib/tempstorage/.... В этом случае после завершения работы с файлом допустимо принудительно очистить память:
&НаКлиенте
Процедура СкачатьИзХранилища(ВладелецФайла, ИмяФайла, НомерФайла)
АдресВХ = ПоместитьФайлВХранилище(ВладелецФайла, ИмяФайла, НомерФайла);
Если ЭтоАдресВременногоХранилища(АдресВХ) Тогда
ПолучитьФайл(АдресВХ, ИмяФайла, Истина);
УдалитьИзВременногоХранилища(АдресВХ);
КонецЕсли;
КонецПроцедуры
СоздатьКлючЗаписи() отработает корректно только тогда, когда в структуру отбора включены все измерения.ПолучитьНавигационнуюСсылку() в метод УдалитьИзВременногоХранилища(). Проверяйте адрес функцией ЭтоАдресВременногоХранилища().e1cib/data/...) работает быстрее и потребляет значительно меньше памяти сервера rphost, так как бинарные данные передаются платформой напрямую, минуя промежуточную загрузку в переменные встроенного языка.