Как решить проблему пустого результата запроса при подключении к внешней базе 1С через COM-соединение?

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

При интеграции различных конфигураций 1С часто возникает необходимость прямого чтения данных из внешней информационной базы. Наиболее популярным классическим инструментом для этого является использование технологии COM-соединения через библиотеку V83.COMConnector. Однако начинающие (а иногда и опытные) разработчики часто сталкиваются с неочевидной проблемой: запрос, написанный во внешней обработке для получения данных из другой базы, возвращает пустой результат или вовсе приводит к ошибкам отображения на форме, хотя тот же самый запрос, запущенный локально в целевой базе, отрабатывает корректно и возвращает заполненную таблицу.

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

В чем заключается суть проблемы и почему данные «исчезают»?

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


Бухгалтерия = Новый COMObject("V83.COMConnector");
СтрокаПодключения = "Srvr=""MyServer"";Ref=""MyBase"";Usr=""Admin"";Pwd=""123"";";
Соединение = Бухгалтерия.Connect(СтрокаПодключения);

Запрос = Соединение.NewObject("Запрос");
Запрос.Текст = "ВЫБРАТЬ Ссылка, Наименование ИЗ Справочник.Номенклатура";

РезультатЗапроса = Запрос.Выполнить().Выгрузить();

// Попытка отобразить данные на форме
ЭлементыФормы.ТЗТовары.Значение = РезультатЗапроса;
ЭлементыФормы.ТЗТовары.СоздатьКолонки();

На первый взгляд код выглядит логично, но при его выполнении табличное поле на форме останется пустым, либо программа вызовет критическое исключение. Выясним причину такого поведения платформы.

Дело в том, что переменная РезультатЗапроса, полученная из внешнего соединения, содержит не локальный объект 1С ТаблицаЗначений, а ссылку на COM-объект, физически находящийся в адресном пространстве другого процесса (внешней базы данных). Когда мы пытаемся напрямую присвоить этот объект элементам формы текущей базы, локальный интерфейс платформы не может напрямую связаться со структурой удаленного объекта. Ему неизвестны типы данных колонок, а сами данные не могут быть отображены стандартными методами визуализации.

Более того, если в выборке присутствуют ссылочные типы данных (например, справочники, документы или перечисления внешней базы), локальная база воспринимает их как «чужеродные» COM-объекты. Попытка обратиться к свойствам или получить представление такой ссылки напрямую в локальном сеансе приведет к ошибке.

Для успешного решения этой задачи нам необходимо выполнить маршалинг данных — то есть перенести информацию из внешней базы в локальную структуру с явным преобразованием всех типов в примитивные типы (числа, строки, даты, булево).


Решение 1: Построчный перенос данных с явным описанием типов (Базовый метод)

Рассмотрим по шагам надежный способ переноса данных. Идея заключается в том, что мы создаем пустую локальную таблицу значений, программно добавляем в нее колонки с жестко заданными локальными типами, а затем обходим выборку из внешней базы в цикле и копируем данные построчно.

Важное правило: в самом тексте запроса во внешней базе мы должны выбирать не ссылки на объекты, а их примитивные реквизиты (например, вместо Номенклатура выбираем Номенклатура.Наименование или Номенклатура.Код).

Ниже представлен детальный пример реализации этого метода:


// Шаг 1. Инициализируем COM-соединение с внешней базой данных
Бухгалтерия = Новый COMObject("V83.COMConnector");
СтрокаПодключения = "Srvr=""slk"";Ref=""rz2008"";Usr=""test"";Pwd=""1"";";

Попытка
    Соединение = Бухгалтерия.Connect(СтрокаПодключения);
Исключение
    Предупреждение("Не удалось подключиться к внешней базе данных!");
    Возврат;
КонецПопытки;

// Шаг 2. Создаем объект запроса во внешней базе через COM-соединение
ЗапросВнешний = Соединение.NewObject("Запрос");
ЗапросВнешний.УстановитьПараметр("ДатаСреза", ТекущаяДата());

// Обратите внимание: выбираем только наименования (строки), количества (числа) и даты!
ЗапросВнешний.Текст = 
    "ВЫБРАТЬ
    |	Штатка.Период КАК Дата,
    |	Штатка.ПодразделениеОрганизации.Наименование КАК Подразделение,
    |	Штатка.Должность.Наименование КАК Должность,
    |	Штатка.КоличествоСтавок КАК КолСтавок,
    |	Штатка.ТарифныйОклад КАК ТО,
    |	Штатка.ДолжностнойОклад КАК ДО
    |ИЗ
    |	РегистрСведений.ШтатноеРасписаниеОрганизаций.СрезПоследних(&ДатаСреза) КАК Штатка";

// Выполняем запрос во внешней базе и получаем выборку
ВыборкаCom = ЗапросВнешний.Выполнить().Выбрать();

// Шаг 3. Создаем локальную таблицу значений на стороне нашей текущей базы
ЛокальнаяТЗ = Новый ТаблицаЗначений;

// Явно описываем типы для каждой колонки
ЛокальнаяТЗ.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));
ЛокальнаяТЗ.Колонки.Добавить("Подразделение", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(150)));
ЛокальнаяТЗ.Колонки.Добавить("Должность", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(150)));
ЛокальнаяТЗ.Колонки.Добавить("КолСтавок", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисел(10, 2)));
ЛокальнаяТЗ.Колонки.Добавить("ТО", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисел(15, 2)));
ЛокальнаяТЗ.Колонки.Добавить("ДО", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисел(15, 2)));

// Шаг 4. Обходим выборку COM-объекта в цикле и заполняем локальную ТЗ
Пока ВыборкаCom.Следующий() Цикл
    НоваяСтрока = ЛокальнаяТЗ.Добавить();
    // Функция ЗаполнитьЗначенияСвойств отлично работает с COM-объектами, 
    // если имена свойств в источнике и приемнике совпадают, а типы примитивные
    ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыборкаCom);
КонецЦикла;

// Шаг 5. Отображаем заполненную локальную таблицу на форме
ЭлементыФормы.ТЗСотрудники.Значение = ЛокальнаяТЗ;
ЭлементыФормы.ТЗСотрудники.СоздатьКолонки();

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


Решение 2: Оптимизация производительности через JSON-сериализацию (Для больших объемов данных)

Предыдущий метод идеален для небольших выборок (до 1000 строк). Однако, если внешняя база данных должна передать десятки тысяч строк, обход выборки COM в цикле приведет к катастрофическому падению производительности. На каждую итерацию цикла тратится драгоценное время из-за накладных расходов сетевого протокола COM (COM overhead).

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

Проанализируем пример быстрой передачи данных через JSON:


// Выполняем запрос и выгружаем результат в ТЗ во внешней базе
ТЗВнешняя = ЗапросВнешний.Выполнить().Выгрузить();

// Сериализуем таблицу во внешней базе в строку JSON
ЗаписьJSON = Соединение.NewObject("ЗаписьJSON");
ЗаписьJSON.УстановитьСтроку();

// Во внешней базе вызываем запись данных
Соединение.ЗаписатьJSON(ЗаписьJSON, ТЗВнешняя);
СтрокаJSON = ЗаписьJSON.Закрыть();

// Возвращаемся в локальную базу и быстро разбираем строку в локальный массив структур
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
МассивДанных = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();

// Создаем локальную таблицу значений
ЛокальнаяТЗ = Новый ТаблицаЗначений;
// Автоматически создаем колонки на основе первой структуры из массива
Если МассивДанных.Количество() > 0 Тогда
    Для Каждого КлючИЗначение Из МассивДанных[0] Цикл
        ЛокальнаяТЗ.Колонки.Добавить(КлючИЗначение.Ключ);
    КонецЦикла;
КонецЕсли;

// Быстро заполняем таблицу
Для Каждого СтрДанных Из МассивДанных Цикл
    ЗаполнитьЗначенияСвойств(ЛокальнаяТЗ.Добавить(), СтрДанных);
КонецЦикла;

Этот метод работает в 10–50 раз быстрее прямого обхода COM-выборки в цикле, так как все тяжелые операции по обработке данных выполняются в памяти одного процесса, а по COM-соединению передается всего один текстовый пакет большого объема.


Решение 3: Передача локальной таблицы значений «внутрь» COM-запроса

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

Напрямую передать объект ТаблицаЗначений в качестве параметра запроса во внешнюю базу нельзя — система выдаст ошибку несовместимости типов. Выясним, как обойти это ограничение.

Решением является использование внутреннего формата представления данных 1С (XML-сериализация через внутренние строки):


// 1. Преобразуем локальную ТЗ во внутреннюю строку 1С
СтрокаТЗ = ЗначениеВСтрокуВнутр(ЛокальнаяТЗ);

// 2. Во внешней базе восстанавливаем таблицу значений из этой строки
ВнешняяТЗ = Соединение.ЗначениеИзСтрокиВнутр(СтрокаТЗ);

// 3. Теперь мы можем передать эту таблицу как параметр во внешний запрос
ЗапросВнешний = Соединение.NewObject("Запрос");
ЗапросВнешний.УстановитьПараметр("ВременнаяТаблица", ВнешняяТЗ);
ЗапросВнешний.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &ВременнаяТаблица КАК ВременнаяТаблица;
    | ВЫБРАТЬ ВТ_Данные.Код ИЗ ВТ_Данные КАК ВТ_Данные";

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


Как решить проблему «Класс не зарегистрирован» (ошибка 0x80040154) на сервере?

При переносе рабочего кода с клиентского компьютера на 64-битный сервер приложений 1С разработчики очень часто сталкиваются со следующей ошибкой запуска регламентных заданий:

«Произошла исключительная ситуация (приложение не зарегистрировано): Класс не зарегистрирован (0x80040154)»

Это происходит потому, что по умолчанию компонента comcntr.dll регистрируется как 32-разрядная библиотека, и 64-битный рабочий процесс сервера 1С (rphost.exe) не может запустить ее напрямую.

Выполним шаги по устранению этой системной проблемы:

  1. Убедитесь, что при установке платформы 1С на сервер был выбран компонент «COM-соединение». Если его нет, запустите установщик 1С в режиме изменения состава компонентов и установите его.
  2. Выполните ручную регистрацию библиотеки. Откройте командную строку от имени Администратора и выполните команду: regsvr32 "C:\Program Files\1cv8\<ВерсияПлатформы>\bin\comcntr.dll"
  3. Если ручная регистрация не помогла, необходимо настроить COM+ приложение-обертку в Windows. Разберем этот процесс подробно по шагам:
    • Нажмите комбинацию клавиш Win + R, введите dcomcnfg и нажмите Enter.
    • В открывшемся окне перейдите по пути: Службы компонентов -> Компьютеры -> Мой компьютер -> Приложения COM+.
    • Кликните правой кнопкой мыши по разделу «Приложения COM+» и выберите Создать -> Приложение.
    • В мастере создания выберите «Создать новое пустую приложение», укажите имя (например, 1C_COM_Connector) и выберите способ активации: Серверное приложение.
    • На шаге выбора учетной записи выберите системную учетную запись или пользователя, от имени которого запущен сервер 1С (например, USR1CV8).
    • Завершите создание приложения. Теперь внутри вашей созданной группы зайдите в папку Компоненты.
    • Кликните правой кнопкой мыши -> Создать -> Компонент. Выберите «Установка новых компонентов» и укажите путь к файлу comcntr.dll из каталога bin установленной платформы 1С.
    • После завершения настройки зайдите в свойства созданного COM+ приложения, перейдите на вкладку Безопасность и снимите флаг «Принудительная проверка доступа для приложений».

Данная настройка позволит 64-битному процессу сервера 1С обращаться к 32-битным методам COM-соединения через системную прослойку Windows (процесс dllhost.exe).


Управление сеансами и утечками памяти: Правила хорошего тона

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

Рекомендуется всегда соблюдать следующие правила:


Современная альтернатива: Почему стоит уходить от COM-соединений?

Несмотря на свою популярность, технология COM-соединения постепенно признается устаревшей. Фирма «1С» рекомендует переходить на современные кроссплатформенные технологии интеграции по следующим причинам:

  1. Зависимость от ОС Windows: COM-объекты являются частью операционной системы Windows. Если вы планируете перевести сервер приложений 1С на ОС семейства Linux (что сейчас является повсеместным стандартом для импортозамещения), ваш программный код с использованием COMObject перестанет работать.
  2. Сложности администрирования: При обновлении версий платформы 1С вам придется регулярно перерегистрировать DLL-библиотеки на серверах и следить за версионной совместимостью.

Рекомендуемой альтернативой является создание и публикация HTTP-сервисов (REST API) в базе-источнике. В таком случае локальная база обращается к внешней через стандартные кроссплатформенные объекты HTTPСоединение и обменивается структурированными данными в формате JSON. Это работает на любой операционной системе, не требует установки дополнительных библиотек и функционирует в разы стабильнее.

← На главную