Как передать параметры и группы номенклатуры в запрос 1С 7.7 через OLE/COM соединение

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При организации интеграции между базами данных 1С (например, при переносе данных из 1С 7.7 в 1С 8.3) разработчики часто используют COM-соединение (OLE). Одним из самых узких мест в такой архитектуре является выполнение запросов к OLE-базе с наложением фильтров по сложным объектам, таким как конкретные элементы справочников, группы номенклатуры или списки значений. Попытка напрямую подставить объект, полученный через OLE, в текст запроса приводит к синтаксической ошибке «Ошибка в выражении».

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

Почему возникает ошибка «Ошибка в выражении» при OLE-запросе?

Разберем механизм работы «черного запроса» 1С 7.7. Когда мы создаем объект запроса через OLE с помощью конструкции БазаОле.CreateObject("Запрос"), выполнение и компиляция этого запроса происходят во внутреннем контексте запущенного OLE-сервера 1С 7.7. Этот контекст полностью изолирован от переменных вызывающей программы (будь то 1С 8.х, скрипт VBScript или приложение на C#).

Если мы пишем условие вида Условие (Товар в ГрТовара), то парсер запросов 1С 7.7 пытается найти переменную с именем ГрТовара внутри своего окружения. Так как эта переменная определена только в коде вызывающей базы (в 1С 8.х), OLE-сервер 1С 7.7 ничего не знает о ней, что и вызывает закономерный сбой инициализации запроса.

Давайте рассмотрим пошагово, какими способами можно обойти это ограничение платформы.

Способ 1. Использование экспортной переменной Глобального модуля (с изменением конфигурации 7.7)

Самый очевидный способ сделать переменную видимой для OLE-запроса — это объявить ее в Глобальном модуле базы-источника 1С 7.7 с ключевым словом Экспорт. В этом случае переменная станет частью глобального контекста системы, к которому у парсера запросов есть прямой доступ.

Выполним следующие действия по шагам:

  1. Откроем конфигуратор базы 1С 7.7.
  2. В Глобальном модуле в самом верху (в секции объявлений переменных) добавим строчку: Перем глОлеГруппаТоваров Экспорт;
  3. Сохраним конфигурацию.

Теперь мы можем записать в эту переменную нужный элемент (группу товаров) перед выполнением запроса. Обратите внимание, что напрямую присвоить значение переменной через точку от COM-объекта (например, БазаОле.глОлеГруппаТоваров = ТекЭл) платформа 1С 8 часто не разрешает, возвращая ошибку «Поле объекта не обнаружено». Для обхода этого ограничения мы используем встроенный метод ExecuteBatch или EvalExpr.

Посмотрим на пример кода на стороне 1С 8.х:


// 1. Инициализируем справочник в OLE-базе и находим нужную группу
ГрТовара = БазаОле.CreateObject("Справочник.Номенклатура");
Если ГрТовара.НайтиПоНаименованию(СокрЛП(ГруппаТов.Наименование), 0, 1) > 0 Тогда
    ТекЭл = ГрТовара.ТекущийЭлемент();
Иначе
    Сообщить("Группа не найдена в базе 1С 7.7!");
    Возврат;
КонецЕсли;

// 2. Помещаем ссылку во внутреннюю строку для безопасной передачи
ВнутрСтрока = БазаОле.ЗначениеВСтрокуВнутр(ТекЭл);

// 3. Заполняем глобальную переменную в контексте 1С 7.7
БазаОле.ExecuteBatch("глОлеГруппаТоваров = ЗначениеИзСтрокиВнутр(""" + СтрЗаменить(ВнутрСтрока, """", """""") + """)");

// 4. Формируем и выполняем запрос
Запрос = БазаОле.CreateObject("Запрос");
ТекстЗапроса = "Период с '" + Формат(ДатаНачала, "ДФ=yyyyMMdd") + "' по '" + Формат(ДатаКонца, "ДФ=yyyyMMdd") + "';
|Товар = Регистр.ПартииТоваров.Товар;
|ОстатокКол = Регистр.ПартииТоваров.ОстатокТовара;
|Группировка Товар без групп;
|Функция КонКол = КонОст(ОстатокКол);
|Условие (Товар в глОлеГруппаТоваров);";

Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
    // Обработка результатов запроса...
КонецЕсли;

Способ 2. Использование списка значений через специальную функцию

Что делать, если нам необходимо передать в условие запроса не одну конкретную группу, а динамический список элементов или несколько разных папок? Для этого отлично подойдет объект СписокЗначений. Чтобы наполнить его, напишем в Глобальном модуле 1С 7.7 универсальную сервисную функцию.

Добавим в Глобальный модуль 1С 7.7 следующий код:


Перем глСписокПараметровЗапроса Экспорт;

Функция глДобавитьВСписокПараметров(ТипОбъекта, ВидОбъекта, ИдентификаторПоиска) Экспорт
    Если ПустоеЗначение(глСписокПараметровЗапроса) = 1 Тогда
        глСписокПараметровЗапроса = СоздатьОбъект("СписокЗначений");
    КонецЕсли;
    
    // Если передан пустой идентификатор — очищаем список
    Если ИдентификаторПоиска = "ОЧИСТИТЬ" Тогда
        глСписокПараметровЗапроса.УдалитьВсе();
        Возврат 1;
    КонецЕсли;
    
    Попытка
        ОбъектПоиска = СоздатьОбъект(ТипОбъекта + "." + ВидОбъекта);
        // Пытаемся найти по коду (можно дописать поиск по наименованию)
        Если ОбъектПоиска.НайтиПоКоду(ИдентификаторПоиска, 0) = 1 Тогда
            глСписокПараметровЗапроса.ДобавитьЗначение(ОбъектПоиска.ТекущийЭлемент());
            Возврат 1;
        КонецЕсли;
    Исключение
        Возврат 0;
    КонецПопытки;
    
    Возврат 0;
КонецФункции

Теперь на стороне 1С 8.х мы можем последовательно вызывать эту функцию через EvalExpr, наполняя глобальный список значений нужными нам элементами, после чего использовать этот список в условии запроса:


// Очищаем список перед новым заполнением
БазаОле.EvalExpr("глДобавитьВСписокПараметров("""", """", ""ОЧИСТИТЬ"")");

// Добавляем нужные группы номенклатуры по их кодам в 1С 7.7
БазаОле.EvalExpr("глДобавитьВСписокПараметров(""Справочник"", ""Номенклатура"", ""00000125"")");
БазаОле.EvalExpr("глДобавитьВСписокПараметров(""Справочник"", ""Номенклатура"", ""00000458"")");

// Запуск OLE-запроса
Запрос = БазаОле.CreateObject("Запрос");
ТекстЗапроса = "Период с '" + Формат(ДатаЗапроса, "ДФ=yyyyMMdd") + "';
|Товар = Регистр.ПартииТоваров.Товар;
|ОстатокКол = Регистр.ПартииТоваров.ОстатокТовара;
|Группировка Товар без групп;
|Условие (Товар в глСписокПараметровЗапроса);";

Способ 3. Перенос выполнения запроса на сторону 1С 7.7 (Рекомендуемый метод)

Рассмотрим очень важный аспект интеграции — скорость работы. Выполнение запросов через OLE напрямую («в лоб») и последующий обход выборки во внешней программе работают крайне медленно. Причина кроется в межпроцессном взаимодействии (маршалинге): при вызове методов Запрос.Группировка() или при получении значения каждого реквизита выборки происходит полноценный сетевой/системный вызов между процессами 1С 8 и 1С 7.7. Если в выборке более 1000 строк, процесс получения данных может растянуться на часы.

Чтобы кардинально ускорить работу (в десятки и сотни раз), настоятельно рекомендуется перенести всю логику формирования, выполнения и первоначальной обработки запроса на сторону 1С 7.7. Локально запрос отработает мгновенно, после чего мы выгрузим результат в локальную ТаблицуЗначений и передадим ее целиком в 1С 8.

Посмотрим, как реализовать этот подход. Напишем в Глобальном модуле 1С 7.7 экспортную функцию:


Функция глПолучитьОстаткиПоГруппе(КодГруппы, ДатаЗапроса) Экспорт
    // Инициализируем группу номенклатуры
    СпрНоменклатура = СоздатьОбъект("Справочник.Номенклатура");
    Если СпрНоменклатура.НайтиПоКоду(КодГруппы, 0) = 0 Тогда
        Возврат "Ошибка: Группа не найдена!";
    КонецЕсли;
    ВыбраннаяГруппа = СпрНоменклатура.ТекущийЭлемент();
    
    // Создаем и выполняем запрос локально в 1С 7.7
    Запрос = СоздатьОбъект("Запрос");
    ТекстЗапроса = "Период с '" + ДатаЗапроса + "' по '" + ДатаЗапроса + "';
    |Товар = Регистр.ПартииТоваров.Товар;
    |ОстатокКол = Регистр.ПартииТоваров.ОстатокТовара;
    |ОстатокСум = Регистр.ПартииТоваров.Стоимость;
    |Группировка Товар без групп;
    |Функция КонКол = КонОст(ОстатокКол);
    |Функция КонСум = КонОст(ОстатокСум);
    |Условие (Товар в ВыбраннаяГруппа);";
    
    Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
        Возврат "Ошибка выполнения запроса!";
    КонецЕсли;
    
    // Выгружаем результаты запроса в Таблицу Значений
    ТабРезультат = СоздатьОбъект("ТаблицаЗначений");
    Запрос.Выгрузить(ТабРезультат, 1, 0);
    
    // Возвращаем объект Таблицы Значений обратно во внешнюю программу
    Возврат ТабРезультат;
КонецФункции

Теперь проанализируем, насколько просто и красиво выглядит вызов этого метода из нашей обработки в 1С 8.х:


// Вызываем функцию 1С 7.7 напрямую, передавая только простые типы данных
КодГруппыВ77 = "00000512"; // Передаем код строкой
ДатаВыгрузки = Формат(Объект.Дата2, "ДФ=yyyyMMdd");

ТаблицаРезультатаОле = БазаОле.глПолучитьОстаткиПоГруппе(КодГруппыВ77, ДатаВыгрузки);

// Проверяем, вернулась ли таблица или строка с ошибкой
Если ТипЗнч(ТаблицаРезультатаОле) = Тип("Строка") Тогда
    Сообщить("Произошла ошибка на стороне 1С 7.7: " + ТаблицаРезультатаОле);
    Возврат;
КонецЕсли;

// Локально обходим полученную таблицу значений OLE на стороне 1С 8
ТаблицаРезультатаОле.ВыбратьСтроки();
Пока ТаблицаРезультатаОле.ПолучитьСтроку() = 1 Цикл
    // Чтение реквизитов из строки OLE-таблицы происходит мгновенно
    НоменклатураНаименование = ТаблицаРезультатаОле.Товар.Наименование;
    Количество = ТаблицаРезультатаОле.КонКол;
    Сумма = ТаблицаРезультатаОле.КонСум;
    
    // Заполняем документы или справочники в нашей новой базе 1С 8
    // ...
КонецЦикла;

При использовании этого метода вы сразу заметите колоссальный прирост производительности. Локальное ядро 1С 7.7 очень быстро выполняет запросы и упаковывает их в оперативную память. Передача одного COM-объекта ТаблицаЗначений занимает доли секунды, а чтение строк из нее происходит в разы быстрее, чем последовательный перебор группировок OLE-запроса.

Способ 4. Получение данных без изменения конфигурации 1С 7.7 (через "Внутреннюю строку")

Бывают ситуации, когда конфигурация 1С 7.7 находится на строгой поддержке, или у разработчика нет возможности/прав на изменение глобального модуля и написание там новых функций. В этом случае мы можем применить продвинутый трюк с использованием сериализации объектов во внутренний формат 1С.

Для реализации этого метода нам понадобится функция ЗначениеИзСтрокиВнутр(), которую мы внедрим прямо в текст формируемого запроса. Таким образом, объект будет воссоздан из строкового представления непосредственно во время выполнения условия.

Разберем пример реализации этого хитрого механизма в коде 1С 8.х:


// 1. Находим нужную группу номенклатуры в базе 1С 7.7
ГрТовара = БазаОле.CreateObject("Справочник.Номенклатура");
Если ГрТовара.НайтиПоНаименованию(СокрЛП(ГруппаТов.Наименование), 0, 1) > 0 Тогда
    ТекЭл = ГрТовара.ТекущийЭлемент();
Иначе
    Сообщить("Не найдена группа в базе 1С 7.7");
    Возврат;
КонецЕсли;

// 2. Получаем строковое представление объекта в формате 1С 7.7
СтроковоеПредставление = БазаОле.ЗначениеВСтрокуВнутр(ТекЭл);

// Обязательно экранируем двойные кавычки внутри строки для корректной вставки в текст запроса
СтроковоеПредставлениеЭкранированное = СтрЗаменить(СтроковоеПредставление, """", """""");

// 3. Формируем запрос, где в условие динамически встраиваем функцию восстановления объекта
ТекстЗапроса = "Период с '" + Формат(Объект.Дата2, "ДФ=yyyyMMdd") + "';
|Товар = Регистр.ПартииТоваров.Товар;
|ОстатокКол = Регистр.ПартииТоваров.ОстатокТовара;
|Группировка Товар без групп;
|Функция КонКол = КонОст(ОстатокКол);
|Условие (Товар в ЗначениеИзСтрокиВнутр(""" + СтроковоеПредставлениеЭкранированная + """));";

Запрос = БазаОле.CreateObject("Запрос");
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
    // Запрос выполнится успешно без каких-либо изменений в глобальном модуле 1С 7.7!
КонецЕсли;

Этот способ является наиболее универсальным, так как не требует монопольного доступа к базе 1С 7.7 для изменения конфигурации и позволяет «на лету» решать задачи интеграции любой сложности.

Резюме и рекомендации

Мы рассмотрели четыре различных способа обхода ограничений контекста при выполнении OLE-запросов к 1С 7.7. Выбор конкретного метода зависит от архитектурных ограничений вашего проекта:

← На главную