При интеграции 1С со сторонними информационными системами часто возникает задача прямого извлечения прикрепленных файлов (документов PDF, сканов, изображений) из базы данных Microsoft SQL Server. На практике разработчики сталкиваются сразу с двумя неочевидными проблемами: ошибкой записи файла на диск объектом 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 на клиентском компьютере, а на самом сервере такой каталог попросту отсутствует. Чтобы устранить данную ошибку, разберем правильный порядок работы с путями:
ПолучитьИмяВременногоФайла(). В этом случае файл запишется в доступный серверу каталог (например, AppData\Local\Temp службы 1С).ДвоичныеДанные, поместить его во временное хранилище через метод ПоместитьВоВременноеХранилище(), после чего на стороне клиента вызвать интерактивное получение файла.Когда файл успешно сохраняется на диск, может возникнуть следующая ситуация: программа просмотра документов (например, 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-архивом, из-за чего и возникает ошибка чтения.
Рассмотрим наиболее простой и производительный вариант решения. Начиная с версии 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С больше не требуется.
Если версия 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 или системной команды ОС) через метод ЗапуститьПриложение().
Объект 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С.