При программной работе с файлами разработчики часто сталкиваются с ситуацией, когда код выполняется без явных синтаксических ошибок, однако файл не сохраняется, оказывается пустым или повреждается. Разберем типичные ошибки при записи файлов в базу данных, выясним причину их возникновения и рассмотрим правильные способы сохранения файлов как в типовых конфигурациях (БСП), так и в произвольных объектах с нуля.
Рассмотрим распространенный фрагмент кода, вызывающий проблемы:
файл = Новый ЧтениеТекста(НовыйПутьКфайлу);
текстданные = файл.Прочитать();
Хранилище = Новый ХранилищеЗначения(текстданные, Новый СжатиеДанных(9));
КопияФайла = Справочники.Файлы.СоздатьЭлемент();
КопияФайла.ТекстХранилище = Хранилище;
КопияФайла.Записать();
Проанализируем, почему этот код работает некорректно:
ЧтениеТекста предназначен исключительно для работы с текстовыми файлами (TXT, CSV, XML, JSON). Если попытаться прочитать им произвольный бинарный файл (например, XLSX, DOCX, PDF, картинку), структура данных будет необратимо повреждена из-за преобразования байтов в символы кодировки. Любые бинарные файлы необходимо читать с помощью объекта ДвоичныеДанные.ТекстХранилище в БСП: в типовых конфигурациях справочник Файлы входит в состав Библиотеки стандартных подсистем (БСП). Реквизит ТекстХранилище не предназначен для хранения самого файла — в него записывается только неформатированный текст, извлеченный для работы механизма полнотекстового поиска. Тело файла в БСП хранится либо в специализированных объектах (например, регистр сведений ДвоичныеДанныеФайлов или справочник ВерсииФайлов), либо на внешних дисковых томах.Клиент (например, РаботаСФайламиСлужебныйКлиент) из серверной процедуры или модуля объекта приводит к ошибке Переменная не определена, так как клиентские модули скомпилированы только для стороны клиента.Разберем базовый механизм сохранения произвольного файла в базу данных, когда в конфигурации создан свой реквизит с типом ХранилищеЗначения.
Если код выполняется на сервере и путь к файлу доступен серверу 1С:
ПутьКФайлу = "D:\Upload\Document.xlsx";
// Получаем файл в виде двоичных данных
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
// Оборачиваем двоичные данные в ХранилищеЗначения со сжатием
Хранилище = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
// Записываем в объект базы данных
НовыйОбъект = Справочники.ХранилищеФайлов.СоздатьЭлемент();
НовыйОбъект.Наименование = "Document.xlsx";
НовыйОбъект.ДанныеФайла = Хранилище;
НовыйОбъект.Записать();
В управляемом приложении клиент не имеет прямого доступа к серверным объектам, а сервер не видит локальные файлы пользователя. Разберем по шагам правильную схему загрузки файла через временное хранилище:
&НаКлиенте
Процедура ЗагрузитьФайл(Команда)
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.Заголовок = "Выберите файл для загрузки";
Оповещение = Новый ОписаниеОповещения("ПослеПомещенияФайла", ЭтотОбъект);
НачатьПомещениеФайлаНаСервер(Оповещение,,, ПараметрыДиалога, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПомещенияФайла(ОписаниеПомещенногоФайла, ДополнительныеПараметры) Экспорт
Если ОписаниеПомещенногоФайла = Неопределено Тогда
Возврат;
КонецЕсли;
СохранитьФайлНаСервере(ОписаниеПомещенногоФайла.Адрес, ОписаниеПомещенногоФайла.СсылкаНаФайл.Имя);
КонецПроцедуры
&НаСервере
Процедура СохранитьФайлНаСервере(АдресВХ, ИмяФайла)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХ);
Элемент = Справочники.ХранилищеФайлов.СоздатьЭлемент();
Элемент.Наименование = ИмяФайла;
Элемент.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
Элемент.Записать();
УдалитьИзВременногоХранилища(АдресВХ);
КонецПроцедуры
Если в конфигурации внедрена подсистема «Работа с файлами» БСП, не следует заполнять реквизиты справочника Файлы вручную. Для этого предусмотрен штатный API. Процедура выполняется на сервере с передачей адреса временного хранилища:
&НаСервере
Процедура ПрикрепитьФайлКОбъектуБСП(АдресВременногоХранилища, ВладелецФайла, ИмяФайла)
ПараметрыФайла = Новый Структура;
ПараметрыФайла.Вставить("Автор", Пользователи.ТекущийПользователь());
ПараметрыФайла.Вставить("ВладелецФайлов", ВладелецФайла);
ПараметрыФайла.Вставить("ИмяБезРасширения", ПолучитьИмяБезРасширения(ИмяФайла));
ПараметрыФайла.Вставить("РасширениеБезТочки", ПолучитьРасширение(ИмяФайла));
ПараметрыФайла.Вставить("ВремяИзмененияУниверсальное", ТекущаяУниверсальнаяДата());
// Штатный метод БСП сохранит файл в базу или на том
НовыйФайл = РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, АдресВременногоХранилища);
КонецПроцедуры
Если цель состоит не просто в хранении файла, а в обработке табличных данных из файла Excel (включая файлы с объединенными ячейками), платформенный объект ТабличныйДокумент позволяет прочитать файл напрямую без использования внешних COM-объектов:
ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ПутьКФайлуExcel);
ТЗ = Новый ТаблицаЗначений;
// Создаем колонки под структуру файла
Для НомерКолонки = 1 По ТабДок.ШиринаТаблицы Цикл
ТЗ.Колонки.Добавить("Колонка" + НомерКолонки);
КонецЦикла;
// Обходим строки документа
Для НомерСтроки = 1 По ТабДок.ВысотаТаблицы Цикл
НоваяСтрока = ТЗ.Добавить();
Для НомерКолонки = 1 По ТабДок.ШиринаТаблицы Цикл
// При объединении ячеек значение всегда хранится в левой верхней ячейке области
НоваяСтрока["Колонка" + НомерКолонки] = ТабДок.Область(НомерСтроки, НомерКолонки).Текст;
КонецЦикла;
КонецЦикла;
Важная рекомендация по оптимизации: сохранение больших файлов непосредственно в реквизитах справочников приводит к росту таблиц и замедлению выборок. Рекомендуется выносить реквизиты с типом ХранилищеЗначения в отдельные регистры сведений либо использовать механизм томов на диске.