Как правильно загрузить файл в хранилище значений в 1С

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

При программной работе с файлами разработчики часто сталкиваются с ситуацией, когда код выполняется без явных синтаксических ошибок, однако файл не сохраняется, оказывается пустым или повреждается. Разберем типичные ошибки при записи файлов в базу данных, выясним причину их возникновения и рассмотрим правильные способы сохранения файлов как в типовых конфигурациях (БСП), так и в произвольных объектах с нуля.

Анализ типичных ошибок при сохранении файлов

Рассмотрим распространенный фрагмент кода, вызывающий проблемы:


файл = Новый ЧтениеТекста(НовыйПутьКфайлу);
текстданные = файл.Прочитать();
Хранилище = Новый ХранилищеЗначения(текстданные, Новый СжатиеДанных(9));
КопияФайла = Справочники.Файлы.СоздатьЭлемент();
КопияФайла.ТекстХранилище = Хранилище;
КопияФайла.Записать();

Проанализируем, почему этот код работает некорректно:

  1. Неправильный тип данных для чтения: объект ЧтениеТекста предназначен исключительно для работы с текстовыми файлами (TXT, CSV, XML, JSON). Если попытаться прочитать им произвольный бинарный файл (например, XLSX, DOCX, PDF, картинку), структура данных будет необратимо повреждена из-за преобразования байтов в символы кодировки. Любые бинарные файлы необходимо читать с помощью объекта ДвоичныеДанные.
  2. Непонимание назначения реквизита ТекстХранилище в БСП: в типовых конфигурациях справочник Файлы входит в состав Библиотеки стандартных подсистем (БСП). Реквизит ТекстХранилище не предназначен для хранения самого файла — в него записывается только неформатированный текст, извлеченный для работы механизма полнотекстового поиска. Тело файла в БСП хранится либо в специализированных объектах (например, регистр сведений ДвоичныеДанныеФайлов или справочник ВерсииФайлов), либо на внешних дисковых томах.
  3. Ошибки контекста выполнения (Клиент / Сервер): попытка вызвать метод общего модуля с постфиксом Клиент (например, РаботаСФайламиСлужебныйКлиент) из серверной процедуры или модуля объекта приводит к ошибке Переменная не определена, так как клиентские модули скомпилированы только для стороны клиента.

Способ 1. Загрузка файла в ХранилищеЗначения «с нуля»

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

Если код выполняется на сервере и путь к файлу доступен серверу 1С:


ПутьКФайлу = "D:\Upload\Document.xlsx";

// Получаем файл в виде двоичных данных
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);

// Оборачиваем двоичные данные в ХранилищеЗначения со сжатием
Хранилище = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));

// Записываем в объект базы данных
НовыйОбъект = Справочники.ХранилищеФайлов.СоздатьЭлемент();
НовыйОбъект.Наименование = "Document.xlsx";
НовыйОбъект.ДанныеФайла = Хранилище;
НовыйОбъект.Записать();

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

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

Способ 2. Программное добавление файла в типовых конфигурациях (БСП)

Если в конфигурации внедрена подсистема «Работа с файлами» БСП, не следует заполнять реквизиты справочника Файлы вручную. Для этого предусмотрен штатный API. Процедура выполняется на сервере с передачей адреса временного хранилища:


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

Как прочитать данные из файла XLSX в ТаблицуЗначений

Если цель состоит не просто в хранении файла, а в обработке табличных данных из файла Excel (включая файлы с объединенными ячейками), платформенный объект ТабличныйДокумент позволяет прочитать файл напрямую без использования внешних COM-объектов:


ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ПутьКФайлуExcel);

ТЗ = Новый ТаблицаЗначений;
// Создаем колонки под структуру файла
Для НомерКолонки = 1 По ТабДок.ШиринаТаблицы Цикл
    ТЗ.Колонки.Добавить("Колонка" + НомерКолонки);
КонецЦикла;

// Обходим строки документа
Для НомерСтроки = 1 По ТабДок.ВысотаТаблицы Цикл
    НоваяСтрока = ТЗ.Добавить();
    Для НомерКолонки = 1 По ТабДок.ШиринаТаблицы Цикл
        // При объединении ячеек значение всегда хранится в левой верхней ячейке области
        НоваяСтрока["Колонка" + НомерКолонки] = ТабДок.Область(НомерСтроки, НомерКолонки).Текст;
    КонецЦикла;
КонецЦикла;

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

← На главную