Как выгрузить и сохранить файл из поля varbinary(max) сторонней базы SQL в 1С

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

При интеграции 1С со сторонними информационными системами часто возникает задача прямого извлечения прикрепленных файлов (документов PDF, сканов, изображений) из базы данных Microsoft SQL Server. На практике разработчики сталкиваются сразу с двумя неочевидными проблемами: ошибкой записи файла на диск объектом ADODB.Stream и повреждением итогового документа после выгрузки. Разберем по шагам причины этих проблем и рассмотрим надежные способы их решения.

Проблема 1: Ошибка «Не удается записать файл» в ADODB.Stream и контекст выполнения

Проанализируем типовой сценарий: через COM-соединение ADODB.Connection выполняется SQL-запрос к таблице файлов, извлекается поле типа varbinary(max), после чего поток ADODB.Stream пытается сохранить данные на диск с помощью вызова Stream.SaveToFile(...).

В этот момент часто возникает исключение:

Ошибка при вызове метода контекста (SaveToFile): Произошла исключительная ситуация (ADODB.Stream): Не удается записать файл.

Выясним причину этой ошибки. Если код 1С исполняется в серверном контексте (например, в общем модуле с признаком «Сервер», в фоновом задании или серверной процедуре формы), обращение к файловой системе происходит от имени системной учетной записи службы сервера 1С (обычно USR1CV83), а не текущего пользователя операционной системы.

Посмотрим на пример ошибочного указания пути:


ИмяФайла = "C:\Users\petrunin_av\Desktop\1.pdf";
Stream.SaveToFile(ИмяФайла, 2);

Серверный процесс физически не имеет доступа к профилю локального пользователя petrunin_av на клиентском компьютере, а на самом сервере такой каталог попросту отсутствует. Чтобы устранить данную ошибку, разберем правильный порядок работы с путями:

  1. Использование временных файлов сервера: при сохранении данных на стороне сервера формируем путь через системную функцию ПолучитьИмяВременногоФайла(). В этом случае файл запишется в доступный серверу каталог (например, AppData\Local\Temp службы 1С).
  2. Передача файла на клиент: если файл требуется открыть или сохранить на компьютере пользователя, не следует организовывать общий сетевой доступ к серверным папкам. Правильный подход — прочитать полученный файл в объект ДвоичныеДанные, поместить его во временное хранилище через метод ПоместитьВоВременноеХранилище(), после чего на стороне клиента вызвать интерактивное получение файла.

Проблема 2: Повреждение файла и сжатие данных (zlib / GZip)

Когда файл успешно сохраняется на диск, может возникнуть следующая ситуация: программа просмотра документов (например, Adobe Acrobat Reader) сообщает, что файл поврежден и не может быть открыт. При этом при ручной выгрузке документа из оригинальной сторонней программы он открывается корректно.

Проверим размеры файлов. Если сравнить количество байтов через функцию Контент.Выгрузить().Количество() и размер файла, выгруженного сторонней программой, то полученный напрямую из базы размер оказывается заметно меньше.

Проверим длину бинарного поля непосредственно на стороне SQL Server:

SELECT DATALENGTH(Content) FROM CuratorDB.dbo.FileData WHERE Oid = '...';

Если длина поля в базе полностью совпадает с количеством байтов, полученных через 1С, значит, сторонняя система перед сохранением в базу сжимает бинарные данные. В частности, структура таблицы с полями Oid, FileName и Content характерна для приложений на платформе DevExpress (XAF / XPO), где бинарный контент по умолчанию упаковывается алгоритмом GZip / Deflate (библиотека zlib). Сохраненный файл с расширением .pdf фактически является сжатым GZip-архивом, из-за чего и возникает ошибка чтения.

Способ 1: Автоматическая декомпрессия на стороне MS SQL Server

Рассмотрим наиболее простой и производительный вариант решения. Начиная с версии Microsoft SQL Server 2016, в T-SQL добавлена встроенная функция DECOMPRESS(). Она принимает сжатый поток байтов GZip и возвращает исходные несжатые данные типа varbinary(max).

Изменим текст SQL-запроса, переложив задачу распаковки на СУБД:


Команда = Новый COMОбъект("ADODB.Command");
Команда.ActiveConnection = СоединениеSQL;
Команда.CommandText = 
    "SELECT 
    |    CuratorDB.dbo.FileData.FileName,
    |    DECOMPRESS(CuratorDB.dbo.FileData.Content) AS Content
    |FROM CuratorDB.dbo.FileData 
    |WHERE Oid = '" + ИдентификаторФайла + "'";

RecordSet = Команда.Execute();

В этом случае в поле Content возвращается уже оригинальный поток байтов документа, и дополнительная обработка архива в 1С больше не требуется.

Способ 2: Распаковка средствами платформы 1С:Предприятие

Если версия MS SQL Server старше 2016 года или декомпрессию требуется выполнить на стороне 1С, разберем работу со встроенными инструментами архивации.

Начиная с версии платформы 8.3.26, объект ЧтениеZipФайла поддерживает прямое чтение архивов формата GZip. Для распаковки достаточно сохранить сжатые данные с расширением .gz и извлечь их стандартным методом:


ИмяАрхива = ПолучитьИмяВременногоФайла("gz");
КаталогРаспаковки = ПолучитьИмяВременногоФайла();

// Записываем сжатый поток из ADODB.Stream
Stream.SaveToFile(ИмяАрхива, 2);

Архив = Новый ЧтениеZipФайла(ИмяАрхива);
Архив.ИзвлечьВсе(КаталогРаспаковки, РежимВосстановленияПутейФайловZIP.НеВосстанавливать);
Архив.Закрыть();

Для платформ ниже версии 8.3.26 объект ЧтениеZipФайла поддерживает исключительно классический формат ZIP. В таком случае распаковку сжатого временного файла выполняют с помощью вызова консольной утилиты (например, 7-Zip или системной команды ОС) через метод ЗапуститьПриложение().

Способ 3: Отказ от ADODB.Stream с передачей данных через Base64

Объект ADODB.Stream возвращает бинарные данные в виде COM-массива COMSafeArray, с которым встроенный язык 1С работает ограниченно. Кроме того, COM-технологии не работают на серверах под управлением Linux. Рассмотрим универсальный способ получения бинарных данных через кодирование в Base64 прямо на стороне SQL Server.

Посмотрим на пример запроса и кода обработки:


// Формируем запрос с преобразованием varbinary(max) в строку Base64
ЗапросSQL = 
    "SELECT 
    |    FileName,
    |    CAST('' AS XML).value('xs:base64Binary(sql:column(""Content""))', 'varchar(max)') AS ContentBase64
    |FROM CuratorDB.dbo.FileData 
    |WHERE Oid = '" + ИдентификаторФайла + "'";

Команда.CommandText = ЗапросSQL;
RecordSet = Команда.Execute();

Если Не RecordSet.EOF() Тогда
    ИмяФайла = RecordSet.Fields.Item("FileName").Value;
    СтрокаBase64 = RecordSet.Fields.Item("ContentBase64").Value;
    
    // Получаем стандартные ДвоичныеДанные без использования ADODB.Stream
    ДвоичныеДанныеФайла = Base64Значение(СтрокаBase64);
    
    // Сохраняем файл на диск средствами платформы 1С
    ПутьДляСохранения = КаталогВременныхФайлов() + ИмяФайла;
    ДвоичныеДанныеФайла.Записать(ПутьДляСохранения);
КонецЕсли;

Такой подход исключает проблемы с COM-объектом ADODB.Stream, позволяет работать напрямую с типом ДвоичныеДанные и легко передавать полученные файлы между клиентом и сервером 1С.

← На главную