При интеграции систем на платформе .NET с 1С через COM Connector разработчики часто сталкиваются с неочевидным поведением объектов. Типичная ситуация: вы запускаете цикл перебора данных из запроса, передаете их в асинхронную очередь (например, через Channel), но на выходе получаете набор абсолютно одинаковых записей — последних в выборке. Разберем подробно, почему это происходит, как правильно «клонировать» данные и почему стандартный подход с COM-объектами работает так медленно.
Рассмотрим ситуацию, описанную автором темы. При использовании метода QueryResultSelection.Next() (в коде C# это data.Next()) мы работаем с однонаправленным курсором. Объект выборки — это не массив данных, а указатель на текущую строку. Когда вы вызываете new OneWithObject(data) и сохраняете ссылку на COM-объект внутри своего класса, вы сохраняете ссылку на итератор, а не на конкретные данные строки.
В асинхронном цикле происходит следующее:
Next().Channel уходит ссылка на один и тот же COM-объект.Важное правило: COM-объекты 1С являются ссылочными типами. Чтобы сохранить состояние строки, необходимо либо выгрузить данные в структуру, не зависящую от состояния курсора, либо копировать значения полей в примитивные типы .NET (string, int, double) непосредственно в момент итерации.
Проанализируем самый простой и эффективный способ избежать проблем с курсором — использование метода Unload() (Выгрузить). Вместо того чтобы перебирать выборку (QueryResultSelection), мы выгружаем результат запроса в таблицу значений, которая представляет собой коллекцию строк в памяти.
// На стороне C# (через COM)
var queryResult = queryObject.Execute();
var table = queryResult.Unload(); // Получаем объект ValueTable
var count = table.Count();
for (int i = 0; i < count; i++)
{
var row = table.Get(i); // Получаем конкретную строку по индексу
// Теперь row ссылается на конкретную область памяти в 1С,
// которая не изменится при переходе к следующему i
await channel.Writer.WriteAsync(new OneWithObject(row));
}
Этот метод решает проблему «одинаковых объектов», так как каждая строка таблицы значений является самостоятельным объектом метаданных в контексте COM-соединения.
Выясним причину, по которой после выгрузки в ValueTable могут перестать быть доступны английские синонимы свойств (например, Description вместо Наименование). В 1С при работе через COM доступность имен зависит от того, как сформирована таблица.
В объекте выборки (Selection) платформа часто предоставляет доступ к обоим языкам через механизмы IDispatch. Однако в ValueTable имена колонок строго соответствуют именам, заданным в запросе или структуре таблицы. Чтобы гарантированно получать данные по нужным именам, используйте псевдонимы в тексте запроса:
ВЫБРАТЬ
МестаХранения.Ссылка КАК Ref,
МестаХранения.Код КАК Code,
МестаХранения.Наименование КАК Description
ИЗ
Справочник.МестаХраненияДел КАК МестаХранения
В этом случае в объекте row (строка таблицы) колонки будут называться именно так, как указано в блоке КАК (AS), и ошибка Exception при обращении к английским именам исчезнет.
Рассмотрим ситуацию с низкой производительностью (1–4 объекта в секунду). Основная причина тормозов — это маршалинг и STA-модель COM. Каждый вызов метода или свойства через точку (например, row.Code) — это отдельный межпроцессный вызов, который требует переключения контекста потока. Если у вас 4000 объектов и у каждого по 10 свойств, вы совершаете 40 000 тяжелых вызовов.
Проанализируем самый быстрый архитектурный подход: передача данных одной строкой. Вместо того чтобы тянуть каждый объект по отдельности, мы заставляем 1С упаковать всё в JSON.
ValueTable в JSON-строку.System.Text.Json или Newtonsoft.Json.Пример кода на стороне 1С (концептуально):
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ТаблицаРезультатов);
Возврат ЗаписьJSON.Закрыть();
Скорость получения 4000 объектов вырастет в десятки раз, так как вместо тысяч COM-вызовов будет выполнен всего один — получение длинной строки.
Разберем критическую ошибку, которая часто приводит к «раздуванию» памяти (Memory Leak). В .NET каждый COM-объект оборачивается в RCW (Runtime Callable Wrapper). Сборщик мусора (GC) не знает, сколько памяти занимает реальный объект внутри процесса 1С, и может не очищать RCW вовремя.
При итерации тысяч объектов необходимо вручную освобождать ресурсы. Рассмотрим правильный цикл:
while (data.Next())
{
var code = data.Code; // Получаем данные
// Обрабатываем данные...
// Если мы получили объект (например, Ссылка), его нужно освободить
// Marshal.ReleaseComObject(data.Ref);
}
Рекомендация: Если вы все же используете COM-перебор, старайтесь сохранять данные в POCO-классы C# (обычные объекты) как можно быстрее, а ссылки на COM-объекты уничтожать через Marshal.ReleaseComObject() сразу после использования.
Подведем итог нашему анализу работы с COM Connector:
Соблюдение этих структурных правил позволит вам создавать надежные и быстрые интеграционные решения между 1С и платформой .NET.