Как сохранить и выгрузить файл из регистра сведений с помощью навигационной ссылки в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке функционала прикрепления файлов в управляемых формах 1С часто возникает необходимость хранить бинарные данные в регистре сведений и затем выгружать или открывать их на стороне клиента. Однако разработчики регулярно сталкиваются с ошибками вида «Неправильный путь к файлу», «Файл не обнаружен» или «Ошибка удаления значения из временного хранилища».

Разберем по шагам, почему возникают эти ошибки, как устроена адресация данных в платформе 1С, как корректно сформировать КлючЗаписи регистра сведений и получить прямую навигационную ссылку на файл без лишней нагрузки на сервер.

Причины возникновения ошибок при работе с файлами в регистре

Проанализируем две основные проблемы, которые приводят к сбоям в коде:

  1. Неполный состав измерений в ключе записи: Регистр сведений — это не ссылочный объект базы данных. У него нет поля Ссылка, как у справочника или документа. Единственным идентификатором конкретной строки в регистре является совокупность всех его измерений (а для периодических регистров — еще и период). Если при создании объекта КлючЗаписи пропустить хотя бы одно измерение, платформа не сможет однозначно определить строку и вернет ссылку на неопределенный набор, из-за чего навигационная ссылка на реквизит не сформируется или будет указывать на несуществующую запись.
  2. Путаница между временным хранилищем и навигационной ссылкой на данные базы: Адрес во временном хранилище начинается с префикса e1cib/tempstorage/... и указывает на оперативную память сеанса или кластера серверов. Навигационная ссылка на ресурс регистра выглядит как e1cib/data/РегистрСведений... и указывает непосредственно на поле в таблице базы данных. Попытка вызвать метод УдалитьИзВременногоХранилища() для адреса базы данных гарантированно вызывает исключение.

Шаг 1. Загрузка файла и запись в регистр сведений

Рассмотрим процесс помещения файла в базу данных. Сначала на клиенте файл помещается во временное хранилище, а затем на сервере считывается и упаковывается в ХранилищеЗначения для сохранения в ресурсе регистра сведений.

Посмотрим на пример кода записи через МенеджерЗаписи:


&НаСервере
Процедура СохранитьФайлВРегистр(ВладелецФайла, ИмяФайла, НомерФайла, АдресВХ)
    
    Если ЭтоАдресВременногоХранилища(АдресВХ) Тогда
        
        // Получаем бинарные данные из временного хранилища
        ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХ);
        
        // Создаем менеджер записи и заполняем ВСЕ измерения
        МенеджерЗаписи = РегистрыСведений.ХранилищеФайлов.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.ВладелецФайла = ВладелецФайла;
        МенеджерЗаписи.ИмяФайла      = ИмяФайла;
        МенеджерЗаписи.НомерФайла    = НомерФайла;
        
        // Записываем файл в ресурс с типом ХранилищеЗначения (со сжатием)
        МенеджерЗаписи.ДвоичныеДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных());
        
        МенеджерЗаписи.Записать(Истина);
        
    КонецЕсли;
    
КонецПроцедуры

Шаг 2. Получение файла через навигационную ссылку (рекомендуемый способ)

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

Главное правило: в структуру для создания ключа записи должны входить абсолютно все измерения регистра.

Разберем серверную функцию формирования ссылки:


&НаСервере
Функция ПолучитьСсылкуНаФайлВРегистре(ВладелецФайла, ИмяФайла, НомерФайла)
    
    Отбор = Новый Структура;
    Отбор.Вставить("ВладелецФайла", ВладелецФайла);
    Отбор.Вставить("ИмяФайла",      СокрЛП(ИмяФайла));
    Отбор.Вставить("НомерФайла",    НомерФайла);
    
    // Создаем ключ записи со ВСЕМИ измерениями регистра
    КлючЗаписиРег = РегистрыСведений.ХранилищеФайлов.СоздатьКлючЗаписи(Отбор);
    
    // Получаем навигационную ссылку непосредственно на ресурс с типом ХранилищеЗначения
    НавигационнаяСсылка = ПолучитьНавигационнуюСсылку(КлючЗаписиРег, "ДвоичныеДанныеФайла");
    
    Возврат НавигационнаяСсылка;
    
КонецФункции

Теперь на клиенте вызываем получение файла. Обратите внимание: метод УдалитьИзВременногоХранилища() вызывать не нужно, так как навигационная ссылка ведет на постоянные данные информационной базы:


&НаКлиенте
Процедура ВыгрузитьФайлНаКлиенте(ВладелецФайла, ИмяФайла, НомерФайла)
    
    СсылкаНаДанные = ПолучитьСсылкуНаФайлВРегистре(ВладелецФайла, ИмяФайла, НомерФайла);
    
    Если ЗначениеЗаполнено(СсылкаНаДанные) Тогда
        // Запускаем интерактивное сохранение файла пользователю
        ПолучитьФайл(СсылкаНаДанные, ИмяФайла, Истина);
    КонецЕсли;
    
КонецПроцедуры

Шаг 3. Альтернативный способ: передача через временное хранилище

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

Посмотрим на пример извлечения через запрос:


&НаСервере
Функция ПоместитьФайлВХранилище(ВладелецФайла, ИмяФайла, НомерФайла)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ХранилищеФайлов.ДвоичныеДанныеФайла КАК ДвоичныеДанныеФайла
        |ИЗ
        |   РегистрСведений.ХранилищеФайлов КАК ХранилищеФайлов
        |ГДЕ
        |   ХранилищеФайлов.ВладелецФайла = &ВладелецФайла
        |   И ХранилищеФайлов.ИмяФайла = &ИмяФайла
        |   И ХранилищеФайлов.НомерФайла = &НомерФайла";
        
    Запрос.УстановитьПараметр("ВладелецФайла", ВладелецФайла);
    Запрос.УстановитьПараметр("ИмяФайла",      СокрЛП(ИмяФайла));
    Запрос.УстановитьПараметр("НомерФайла",    НомерФайла);
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        Хранилище = Выборка.ДвоичныеДанныеФайла;
        Если ЗначениеЗаполнено(Хранилище) Тогда
            ДвоичныеДанные = Хранилище.Получить();
            // Помещаем во временное хранилище с привязкой к форме
            Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
        КонецЕсли;
    КонецЕсли;
    
    Возврат "";
    
КонецФункции

При использовании этого варианта адрес действительно будет иметь формат e1cib/tempstorage/.... В этом случае после завершения работы с файлом допустимо принудительно очистить память:


&НаКлиенте
Процедура СкачатьИзХранилища(ВладелецФайла, ИмяФайла, НомерФайла)
    
    АдресВХ = ПоместитьФайлВХранилище(ВладелецФайла, ИмяФайла, НомерФайла);
    
    Если ЭтоАдресВременногоХранилища(АдресВХ) Тогда
        ПолучитьФайл(АдресВХ, ИмяФайла, Истина);
        УдалитьИзВременногоХранилища(АдресВХ);
    КонецЕсли;
    
КонецПроцедуры

Важные практические выводы

  1. Всегда передавайте полный набор измерений: В регистре сведений не бывает ссылки на запись без указания всех ключевых полей. Метод СоздатьКлючЗаписи() отработает корректно только тогда, когда в структуру отбора включены все измерения.
  2. Проверяйте тип адреса перед удалением: Никогда не передавайте результат функции ПолучитьНавигационнуюСсылку() в метод УдалитьИзВременногоХранилища(). Проверяйте адрес функцией ЭтоАдресВременногоХранилища().
  3. Экономьте оперативную память: Способ с получением прямой навигационной ссылки на ресурс (e1cib/data/...) работает быстрее и потребляет значительно меньше памяти сервера rphost, так как бинарные данные передаются платформой напрямую, минуя промежуточную загрузку в переменные встроенного языка.
← На главную