При интеграции различных конфигураций 1С часто возникает необходимость прямого чтения данных из внешней информационной базы. Наиболее популярным классическим инструментом для этого является использование технологии COM-соединения через библиотеку V83.COMConnector. Однако начинающие (а иногда и опытные) разработчики часто сталкиваются с неочевидной проблемой: запрос, написанный во внешней обработке для получения данных из другой базы, возвращает пустой результат или вовсе приводит к ошибкам отображения на форме, хотя тот же самый запрос, запущенный локально в целевой базе, отрабатывает корректно и возвращает заполненную таблицу.
В рамках этой статьи мы подробно разберем, почему возникает эта проблема, проанализируем внутренние механизмы взаимодействия баз по протоколу COM, а также рассмотрим несколько эффективных способов решения — от простого заполнения таблицы значений до оптимизированных методов сериализации для высоконагруженных систем.
Рассмотрим стандартную ситуацию. Разработчик подключается к внешней базе данных, создает там объект запроса, выполняет его и пытается выгрузить результат в таблицу значений на форме:
Бухгалтерия = Новый COMObject("V83.COMConnector");
СтрокаПодключения = "Srvr=""MyServer"";Ref=""MyBase"";Usr=""Admin"";Pwd=""123"";";
Соединение = Бухгалтерия.Connect(СтрокаПодключения);
Запрос = Соединение.NewObject("Запрос");
Запрос.Текст = "ВЫБРАТЬ Ссылка, Наименование ИЗ Справочник.Номенклатура";
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
// Попытка отобразить данные на форме
ЭлементыФормы.ТЗТовары.Значение = РезультатЗапроса;
ЭлементыФормы.ТЗТовары.СоздатьКолонки();
На первый взгляд код выглядит логично, но при его выполнении табличное поле на форме останется пустым, либо программа вызовет критическое исключение. Выясним причину такого поведения платформы.
Дело в том, что переменная РезультатЗапроса, полученная из внешнего соединения, содержит не локальный объект 1С ТаблицаЗначений, а ссылку на COM-объект, физически находящийся в адресном пространстве другого процесса (внешней базы данных). Когда мы пытаемся напрямую присвоить этот объект элементам формы текущей базы, локальный интерфейс платформы не может напрямую связаться со структурой удаленного объекта. Ему неизвестны типы данных колонок, а сами данные не могут быть отображены стандартными методами визуализации.
Более того, если в выборке присутствуют ссылочные типы данных (например, справочники, документы или перечисления внешней базы), локальная база воспринимает их как «чужеродные» COM-объекты. Попытка обратиться к свойствам или получить представление такой ссылки напрямую в локальном сеансе приведет к ошибке.
Для успешного решения этой задачи нам необходимо выполнить маршалинг данных — то есть перенести информацию из внешней базы в локальную структуру с явным преобразованием всех типов в примитивные типы (числа, строки, даты, булево).
Рассмотрим по шагам надежный способ переноса данных. Идея заключается в том, что мы создаем пустую локальную таблицу значений, программно добавляем в нее колонки с жестко заданными локальными типами, а затем обходим выборку из внешней базы в цикле и копируем данные построчно.
Важное правило: в самом тексте запроса во внешней базе мы должны выбирать не ссылки на объекты, а их примитивные реквизиты (например, вместо Номенклатура выбираем Номенклатура.Наименование или Номенклатура.Код).
Ниже представлен детальный пример реализации этого метода:
// Шаг 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. Отображаем заполненную локальную таблицу на форме
ЭлементыФормы.ТЗСотрудники.Значение = ЛокальнаяТЗ;
ЭлементыФормы.ТЗСотрудники.СоздатьКолонки();
Этот подход гарантирует стабильность приложения, исключает конфликты типов и корректно отрисовывает данные в интерфейсе пользователя.
Предыдущий метод идеален для небольших выборок (до 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-соединению передается всего один текстовый пакет большого объема.
Часто разработчики сталкиваются с обратной задачей: необходимо передать локальную таблицу значений из текущей базы во внешнюю базу, чтобы использовать ее там в качестве параметра (например, для левого соединения в запросе).
Напрямую передать объект ТаблицаЗначений в качестве параметра запроса во внешнюю базу нельзя — система выдаст ошибку несовместимости типов. Выясним, как обойти это ограничение.
Решением является использование внутреннего формата представления данных 1С (XML-сериализация через внутренние строки):
// 1. Преобразуем локальную ТЗ во внутреннюю строку 1С
СтрокаТЗ = ЗначениеВСтрокуВнутр(ЛокальнаяТЗ);
// 2. Во внешней базе восстанавливаем таблицу значений из этой строки
ВнешняяТЗ = Соединение.ЗначениеИзСтрокиВнутр(СтрокаТЗ);
// 3. Теперь мы можем передать эту таблицу как параметр во внешний запрос
ЗапросВнешний = Соединение.NewObject("Запрос");
ЗапросВнешний.УстановитьПараметр("ВременнаяТаблица", ВнешняяТЗ);
ЗапросВнешний.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &ВременнаяТаблица КАК ВременнаяТаблица;
| ВЫБРАТЬ ВТ_Данные.Код ИЗ ВТ_Данные КАК ВТ_Данные";
Важное ограничение: этот метод гарантированно сработает только в том случае, если локальная таблица содержит исключительно примитивные типы данных (числа, строки, даты), так как ссылки на объекты текущей базы не имеют никакого смысла во внешней базе.
При переносе рабочего кода с клиентского компьютера на 64-битный сервер приложений 1С разработчики очень часто сталкиваются со следующей ошибкой запуска регламентных заданий:
«Произошла исключительная ситуация (приложение не зарегистрировано): Класс не зарегистрирован (0x80040154)»
Это происходит потому, что по умолчанию компонента comcntr.dll регистрируется как 32-разрядная библиотека, и 64-битный рабочий процесс сервера 1С (rphost.exe) не может запустить ее напрямую.
Выполним шаги по устранению этой системной проблемы:
regsvr32 "C:\Program Files\1cv8\<ВерсияПлатформы>\bin\comcntr.dll"
Win + R, введите dcomcnfg и нажмите Enter.1C_COM_Connector) и выберите способ активации: Серверное приложение.USR1CV8).comcntr.dll из каталога bin установленной платформы 1С.Данная настройка позволит 64-битному процессу сервера 1С обращаться к 32-битным методам COM-соединения через системную прослойку Windows (процесс dllhost.exe).
Каждое открытое через V83.COMConnector соединение создает полноценную сессию на сервере внешней базы данных, расходуя оперативную память и лицензии 1С. Если вы не закроете соединение корректно, сессия может оставаться «зависшей» в памяти сервера неограниченное время.
Рекомендуется всегда соблюдать следующие правила:
Попытка
// Работа с данными
Исключение
// Обработка ошибок
КонецПопытки;
// Явное освобождение памяти
ВыборкаCom = Неопределено;
ЗапросВнешний = Неопределено;
Соединение = Неопределено;
Бухгалтерия = Неопределено;
Бухгалтерия = Новый COMObject("V83.COMConnector");
// Максимальное количество удерживаемых открытых соединений в пуле
Бухгалтерия.PoolCapacity = 10;
// Время ожидания освобождения соединения в пуле (в секундах)
Бухгалтерия.PoolTimeout = 60;
Несмотря на свою популярность, технология COM-соединения постепенно признается устаревшей. Фирма «1С» рекомендует переходить на современные кроссплатформенные технологии интеграции по следующим причинам:
COMObject перестанет работать.Рекомендуемой альтернативой является создание и публикация HTTP-сервисов (REST API) в базе-источнике. В таком случае локальная база обращается к внешней через стандартные кроссплатформенные объекты HTTPСоединение и обменивается структурированными данными в формате JSON. Это работает на любой операционной системе, не требует установки дополнительных библиотек и функционирует в разы стабильнее.