Интеграция между 1С и внешними приложениями на языке C# часто реализуется через технологию COM Connector (V83.ComConnector). Одной из наиболее трудоемких задач в этом процессе является извлечение данных из табличных частей документов или справочников. Рассмотрим подробно, как правильно организовать этот процесс, избежать типичных ошибок с типами данных и оптимизировать производительность вашего решения.
При выполнении запроса в 1С через COM Connector, если в тексте запроса указана выборка всей ссылки или обращение к полю табличной части, C# получает объект типа __ComObject. Попытка простого перебора такой коллекции (например, через foreach) в .NET обычно приводит к ошибке "IEnumerable не реализован", так как COM-объекты 1С не поддерживают стандартные интерфейсы коллекций .NET напрямую.
Рассмотрим ситуацию: мы получили объект запроса и пытаемся прочитать табличную часть ВопросыОбращения из справочника ВходящиеДокументы. Типичный код на C# с использованием dynamic выглядит так:
var query = comConnector.НовыйОбъект("Запрос");
query.Текст = "ВЫБРАТЬ Ссылка, ДатаСоздания, Комментарий, ВопросыОбращения ИЗ Справочник.ВходящиеДокументы";
var queryResult = query.Выполнить().Выбрать();
while(queryResult.Следующий())
{
var date = queryResult.ДатаСоздания;
var questions = queryResult.ВопросыОбращения; // Здесь мы получаем COM-объект табличной части
// Что делать дальше?
}
Самый простой способ получить данные из табличной части — вызвать метод Выбрать(). Однако при работе через COM Connector в C# рекомендуется использовать англоязычные аналоги методов. Это связано с тем, что динамическая привязка (dynamic) в .NET иногда некорректно обрабатывает кириллические имена методов COM-сервера.
Проанализируем исправленный цикл перебора:
while(queryResult.Next()) // Используем Next() вместо Следующий()
{
var questions = queryResult.ВопросыОбращения;
var selection = questions.Select(); // Вызываем метод Select (Выбрать) у объекта табличной части
while(selection.Next())
{
// Теперь мы можем обращаться к полям конкретной строки табличной части
var subject = selection.ТемаВопроса;
var text = selection.ТекстВопроса;
}
}
Важный нюанс: Метод Select() возвращает объект выборки (Selection), по которому можно перемещаться только вперед с помощью Next().
Если вам необходимо знать количество строк в табличной части или обращаться к строкам по индексу, лучше использовать метод Unload() (Выгрузить). Он превращает табличную часть в объект ValueTable (ТаблицаЗначений), который обладает более гибким интерфейсом.
Разберем этот подход по шагам:
ValueTable.Count() (Количество), чтобы определить размер коллекции.Get(index) (Получить), чтобы извлечь конкретную строку по ее порядковому номеру.
var questionsTable = queryResult.ВопросыОбращения.Unload(); // Выгружаем в таблицу значений
int count = (int)questionsTable.Count();
for (int i = 0; i < count; i++)
{
var row = questionsTable.Get(i); // Получаем строку по индексу
var questionValue = row.ТекстВопроса;
Console.WriteLine($"Строка {i}: {questionValue}");
}
С точки зрения производительности 1С, обращение queryResult.ВопросыОбращения внутри цикла является "тяжелой" операцией. Каждый раз, когда вы обращаетесь к этому свойству, система выполняет неявный дополнительный запрос к базе данных, чтобы подтянуть данные табличной части для конкретного объекта.
Проанализируем ситуацию: если у вас 1000 документов, вы выполните 1 основной запрос и 1000 дополнительных. Это значительно замедляет работу. Рассмотрим более эффективный способ — плоский запрос.
Вместо того чтобы выбирать документ и лезть в его свойства, выберем сразу таблицу строк:
query.Текст = @"ВЫБРАТЬ
Входящие.Ссылка КАК ДокументСсылка,
Входящие.ДатаСоздания,
Вопросы.ТекстВопроса,
Вопросы.НомерСтроки
ИЗ
Справочник.ВходящиеДокументы КАК Входящие
ЛЕВОЕ СОЕДИНЕНИЕ
Справочник.ВходящиеДокументы.ВопросыОбращения КАК Вопросы
ПО
Вопросы.Ссылка = Входящие.Ссылка";
При таком подходе вы получите один плоский набор данных, где каждая строка — это строка табличной части, дополненная реквизитами "шапки" документа. Это в десятки раз быстрее при больших объемах данных.
Работа с COM Connector в .NET коварна тем, что среда исполнения (CLR) не знает, когда освобождать ресурсы 1С. Каждая переменная queryResult, questions, row — это отдельный COM-объект. Если их не освобождать вручную, процесс dllhost.exe на сервере будет бесконечно раздуваться в памяти, что приведет к падению системы.
Правильный шаблон работы должен выглядеть так:
dynamic questions = null;
dynamic selection = null;
try
{
questions = queryResult.ВопросыОбращения;
selection = questions.Select();
while(selection.Next())
{
// Логика обработки
}
}
finally
{
if (selection != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(selection);
if (questions != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(questions);
}
Запомните: Всегда используйте try-finally и освобождайте каждый объект 1С, полученный через точку или метод.
При получении значений из 1С в C# важно учитывать следующие моменты:
decimal. Если вам нужен int или double, выполняйте явное приведение типов.DateTime, но пустая дата 1С превращается в 01.01.0001. Обязательно проверяйте это значение перед обработкой.СправочникСсылка.Номенклатура), вы получите еще один COM-объект. Чтобы получить строку, вызывайте selection.Item.Description (Наименование) или используйте метод 1С String().Если ваша конфигурация 1С работает на платформе 8.3, рассмотрите возможность отказа от COM в пользу OData (REST API). В 1С:Документооборот этот интерфейс включается одной галочкой в публикации. Для C# разработчика это гораздо удобнее:
Подводя итог, работа через COM Connector остается актуальной для старых систем (например, УПП), но требует дисциплины в именовании методов (английский язык) и строгого контроля за освобождением ресурсов через Marshal.ReleaseComObject.