Как исправить ошибку ADODB "Операция не допускается, если объект закрыт" и правильно загрузить Excel в 1С?

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

Часто при разработке механизмов интеграции и загрузки данных программисты 1С сталкиваются с необходимостью чтения файлов Excel. Классическим способом прошлых лет было использование технологии ADODB (ActiveX Data Objects Database). Однако, этот метод таит в себе множество подводных камней.

Разберем типичную ситуацию: при попытке узнать количество строк в считанном листе возникает ошибка: "Произошла исключительная ситуация (ADODB.Recordset): Операция не допускается, если объект закрыт.". Кроме того, данная ошибка может быть «плавающей»: у одних пользователей она появляется, у других нет. Выясним причину этой проблемы и рассмотрим современные альтернативы.

Разберем причину ошибки в коде ADODB

Посмотрим на фрагмент кода, в котором разработчик осуществляет чтение данных листа в объект ADODB.Recordset и проверяет его на заполненность:


Попытка
    ADODBRecordset = Новый COMОбъект("ADODB.Recordset");
    ADODBRecordset.Open(ТекстЗапроса, ADODBConnection);
    
    // Проверка заполненности листа.
    Если (ADODBRecordset.EOF ИЛИ ADODBRecordset.BOF) Тогда
        КолвоСтрокExcel = 0;
        Сообщить(НСтр("ru = '" + ИмяЛиста + ": не содержит данных.'"), СтатусСообщения.Внимание);
        
        // Закрытие Объектов.
        ADODBRecordset.Close();
        ADODBConnection.Close();
        ADODBRecordset   = Неопределено;
        ADODBConnection = Неопределено;
        
        //Возврат Новый ТаблицаЗначений;    // ЗАКОММЕНТИРОВАНО!
    КонецЕсли;
    
    // ...
Исключение
    // ...
КонецПопытки;

// Ошибка возникает на следующей строке:
КолвоСтрокExcel = ADODBRecordset.RecordCount + 1;

Проанализируем ситуацию по шагам. Ошибка кроется в банальной логике обработки условий:

  1. Программа выполняет запрос к листу Excel. Если лист оказывается пустым (нет записей), срабатывает условие ADODBRecordset.EOF ИЛИ ADODBRecordset.BOF.
  2. Внутри условия происходит штатное закрытие объектов: вызываются методы Close() для рекордсета и коннекшена, а переменным присваивается Неопределено.
  3. Однако оператор прерывания процедуры Возврат был закомментирован разработчиком.
  4. Из-за отсутствия возврата алгоритм не прерывается и идет дальше по тексту модуля.
  5. Дойдя до строки КолвоСтрокExcel = ADODBRecordset.RecordCount + 1;, программа обращается к объекту ADODBRecordset, который мы только что принудительно закрыли и уничтожили. Платформа закономерно выбрасывает исключение: "Операция не допускается, если объект закрыт".

Чтобы починить именно этот участок кода, достаточно раскомментировать инструкцию Возврат, чтобы после закрытия объектов программа корректно выходила из функции.

Скрытые нюансы и проблемы технологии ADODB

Даже если исправить логическую ошибку, чтение файлов `.xls` и `.xlsx` через ADODB — процесс крайне капризный. Рассмотрим подробнее, почему от этого подхода в мире 1С сейчас активно отказываются:

  1. Проблема разрядности (32-бит vs 64-бит). Драйвер Microsoft.Jet.OLEDB.4.0 (для старых файлов xls) существует только в 32-битной версии. Если сервер 1С запущен в 64-битной среде, подключиться через него не выйдет. Драйвер Microsoft.ACE.OLEDB.12.0 имеет 64-битную версию, но она конфликтует с установленным 32-битным MS Office.
  2. Определение типов данных и параметр IMEX=1. В строке подключения часто пишут IMEX=1, чтобы весь смешанный контент колонки считывался как текст. Однако провайдер по умолчанию анализирует только первые 8 строк таблицы (параметр реестра TypeGuessRows). Если первые 8 строк содержат числа, колонка получит числовой тип, и все строковые значения ниже 8-й строки превратятся в Null.
  3. Синтаксис имен листов. Если имя листа содержит пробелы или специальные символы, запрос вида SELECT * FROM [ИмяЛиста$] может вернуть ошибку. Требуется строгая нормализация имен.
  4. Утечки памяти и блокировки файлов. В некоторых ситуациях (особенно при ошибках внутри цикла) ADODB может не освободить процесс Excel в памяти, из-за чего файл остается заблокированным для изменения другими пользователями до перезапуска службы сервера 1С.

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

Если мы решим уйти от ADODB и попытаемся использовать родной объект 1С ТабличныйДокумент, мы можем столкнуться с новой преградой: "Ошибка при выполнении файловой операции" при вызове метода Прочитать(). Выясним причину.

Когда код исполняется на сервере 1С (в серверном контексте), метод пытается найти файл по указанному пути именно на дисках сервера, а не на компьютере пользователя (клиента).

Если файл открыт в Excel на клиенте или серверная служба 1С (запущенная под локальным системным пользователем USR1CV8) не имеет прав доступа к указанной сетевой директории, мы получим ошибку файловой операции.

Как правильно передать файл с клиента на сервер:

  1. На стороне клиента получить двоичные данные файла.
  2. Поместить их во временное хранилище (метод ПоместитьВоВременноеХранилище()).
  3. Передать адрес хранилища на сервер.
  4. На сервере получить файл из временного хранилища (ПолучитьИзВременногоХранилища()).
  5. Записать данные во временный файл на сервере (ПолучитьИмяВременногоФайла()) и уже этот локальный серверный файл считывать в системе.

Современное решение: ТабличныйДокумент и ПостроительЗапроса

Вместо использования устаревших COM-объектов ADODB, платформа 1С:Предприятие 8 позволяет реализовать загрузку данных из Excel невероятно компактно и быстро. Рассмотрим изящный метод, укладывающийся всего в 8 строк кода. Данный способ использует связку ТабличныйДокумент и ПостроительЗапроса.

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


// 1. Создаем пустой табличный документ
ТабДок = Новый ТабличныйДокумент;

// 2. Читаем Excel-файл (поддерживаются и xls, и xlsx) напрямую в документ
ТабДок.Прочитать(ПолноеИмяФайлаНаСервере, СпособЧтенияЗначенийТабличногоДокумента.Значение);

// 3. Создаем ПостроительЗапроса
ПЗ = Новый ПостроительЗапроса;

// 4. Передаем заполненную область табличного документа как источник данных
ПЗ.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТабДок.Область());

// 5. Отключаем автоматическое добавление представлений для скорости
ПЗ.ДобавлениеПредставлений = ТипДобавленияПредставлений.НеДобавлять;

// 6. Инициализируем настройки
ПЗ.ЗаполнитьНастройки();

// 7. Выполняем псевдо-запрос
ПЗ.Выполнить();

// 8. Выгружаем результат в готовую Таблицу Значений
ТаблицаРезультат = ПЗ.Результат.Выгрузить();

Разберем по шагам, почему этот способ лучше:

Используйте этот современный подход для небольших и средних по объему файлов Excel. Это убережет вас от плавающих ошибок COM-соединений, проблем с разрядностью серверов и сэкономит десятки строк трудночитаемого кода.

← На главную