При работе с внешними базами данных MS SQL Server из среды 1С:Предприятие через механизм ADO (ActiveX Data Objects) разработчики часто сталкиваются с исключением: «Произошла исключительная ситуация (ADODB.Recordset): Operation is not allowed when the object is closed». Эта ошибка возникает в тот момент, когда мы пытаемся обратиться к результатам запроса (например, проверить RecordSet.EOF), но объект Recordset оказывается закрытым, хотя сам запрос вроде бы выполнился без ошибок.
В этой статье мы подробно разберем причины возникновения этой ситуации и рассмотрим пошаговые инструкции по ее устранению, основываясь на практическом опыте решения подобных задач.
Проанализируем механику взаимодействия SQL Server и ADO. По умолчанию после выполнения любой команды манипуляции данными (INSERT, UPDATE, DELETE, SELECT INTO) SQL Server отправляет клиенту сообщение о количестве затронутых строк. Для ADO это сообщение выглядит как отдельный, но пустой и закрытый набор данных (Recordset).
Когда вы выполняете сложный пакет запросов с временными таблицами, первым «результатом», который получает 1С, становится именно такое техническое сообщение. Поскольку оно не содержит строк, Recordset помечается как закрытый, и любая попытка работы с ним вызывает ошибку.
Решение: Использование инструкции SET NOCOUNT ON. Рассмотрим, как правильно добавить ее в текст запроса прямо в коде 1С:
ТекстЗапроса = "
|SET NOCOUNT ON;
|SELECT * INTO #TempTable FROM MyTable WHERE Condition = 1;
|SELECT * FROM #TempTable;
|DROP TABLE #TempTable;";
Установка SET NOCOUNT ON в самом начале текста запроса подавляет отправку сообщений о количестве строк, и ADO сразу переходит к первому набору данных, содержащему фактические записи SELECT. Это самый простой и эффективный способ решения проблемы в 90% случаев.
Разберем ситуацию, когда запрос содержит создание временных таблиц (инструкция INTO #temp) и последующую выборку из них. В ADO выполнение такого пакета может быть интерпретировано неоднозначно, особенно если используются устаревшие драйверы соединения.
Если SET NOCOUNT ON не помогает или не может быть использован, проанализируем альтернативный подход — разделение выполнения пакета на отдельные команды в рамках одного соединения.
Посмотрим на пример реализации:
// 1. Создаем соединение
Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.Open(СтрокаСоединения);
// 2. Первый этап: Создаем временную таблицу
Текст1 = "SELECT ID, Name INTO #TempData FROM Catalog WHERE Active = 1";
Соединение.Execute(Текст1);
// 3. Второй этап: Выбираем данные из временной таблицы
Текст2 = "SELECT * FROM #TempData ORDER BY Name";
Рекордсет = Соединение.Execute(Текст2);
// Теперь Рекордсет будет открыт и готов к итерации
Пока Рекордсет.EOF = 0 Цикл
// Обработка данных
Рекордсет.MoveNext();
КонецЦикла;
Важный нюанс: Временные таблицы с префиксом # существуют только в рамках текущей сессии (соединения). Пока объект Connection в 1С жив, временная таблица будет доступна для последующих вызовов Execute.
Иногда запрос намеренно возвращает несколько таблиц. В этом случае Recordset, возвращаемый методом Execute, будет указывать только на первый набор. Чтобы добраться до последующих, необходимо использовать метод NextRecordset().
Выясним, как правильно организовать перебор всех результатов, чтобы избежать ошибки закрытого объекта:
Запрос = "SELECT 1; SELECT 2;";
RecordSet = Connection.Execute(Запрос);
Пока RecordSet <> Неопределено Цикл
Если RecordSet.State = 1 Тогда // 1 = adStateOpen
// Читаем данные текущего набора
КонецЕсли;
// Переходим к следующему набору (пропуская технические сообщения)
RecordSet = RecordSet.NextRecordset();
КонецЦикла;
Если работа с временными таблицами (#temp) вызывает постоянные сложности, рассмотрим архитектурное решение — трансформацию запроса. Вместо промежуточного сохранения данных в таблицы можно использовать Common Table Expressions (CTE) или вложенные подзапросы.
Проанализируем пример трансформации. Вместо последовательного создания таблиц:
-- Было (может вызывать ошибку в ADO)
SELECT ID INTO #t1 FROM Table1;
SELECT * FROM #t1;
Используем конструкцию WITH (CTE):
ТекстЗапроса = "
|WITH CTE_Data AS (
| SELECT ID, Name FROM Table1 WHERE Status = 'Active'
|)
|SELECT * FROM CTE_Data WHERE Name LIKE 'А%'";
RecordSet = Connection.Execute(ТекстЗапроса);
Такой подход гарантирует, что SQL Server вернет ровно один результирующий набор данных, что идеально подходит для работы через COMОбъект.
Помимо структуры самого SQL-кода, на стабильность работы влияют параметры драйвера и свойства объекта Connection в 1С. Рассмотрим несколько профессиональных советов:
{SQL Server} (ODBC) рекомендуется использовать OLE DB провайдеры, например, Provider=SQLNCLI11; (SQL Server Native Client) или современный Provider=MSOLEDBSQL;.CursorLocation = 3 (adUseClient) перед открытием соединения часто помогает избежать проблем с закрытием потока данных. Это заставляет ADO кэшировать данные на стороне клиента (в памяти процесса 1С).Connection не выходит из области видимости раньше, чем вы закончите перебор строк в Recordset. Если соединение закроется, все связанные с ним наборы данных станут недоступны.Подводя итог, можно сказать, что решение проблемы чаще всего кроется в добавлении SET NOCOUNT ON или в отказе от использования временных таблиц в пользу CTE. Проанализировав структуру вашего запроса и применив один из вышеперечисленных методов, вы сможете обеспечить стабильную работу 1С с внешними данными без досадных ошибок обращения к закрытым объектам. Для организации передачи информации во внешнюю БД без ручного программирования COM-соединений можно использовать подсистему выгрузки данных во внешнюю базу SQL.