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

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

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

Разбор типичной ошибки: почему падает код при обходе реквизитов

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


Процедура ТыкНаСервере(ОбъектБД)
    МетаданныеОбъектБД = Метаданные.НайтиПоТипу(ТипЗнч(ОбъектБД));
    Для Каждого Реквизит Из МетаданныеОбъектБД.Реквизиты Цикл
        Сообщить(Реквизит + " = " + ОбъектБД[Реквизит.Имя]); // Ошибка!
    КонецЦикла;
КонецПроцедуры

При выполнении этого кода платформа выбрасывает исключение: "Преобразование значения к типу Число не может быть выполнено". Выясним причину такого поведения системы.

Дело в том, что переменная Реквизит в цикле является объектом метаданных (тип ОбъектМетаданных), а не строкой. Платформа 1С при выполнении операции сложения Реквизит + " = " пытается неявно привести типы. В конфигураторе перегруженный оператор "+" для объектов метаданных не определен как конкатенация строк. Платформа делает попытку преобразовать первый операнд к числу, что закономерно вызывает ошибку времени выполнения.

Для исправления этой ситуации нам необходимо обращаться к свойству Имя объекта метаданных, которое гарантированно возвращает строковое представление (тип Строка):

Сообщить(Реквизит.Имя + " = " + ОбъектБД[Реквизит.Имя]);

Также, если по какой-то причине требуется вывести строковое описание самого объекта метаданных целиком, можно использовать явное приведение к строке с помощью пустых кавычек: "" + Реквизит.

Правильный базовый алгоритм обхода реквизитов шапки

Давайте оптимизируем код получения метаданных. Вместо вызова тяжелого метода Метаданные.НайтиПоТипу(ТипЗнч(ОбъектБД)) мы можем обратиться к свойству Метаданные самого объекта напрямую. Это работает быстрее и выглядит лаконичнее. Напишем рабочий вариант обхода реквизитов шапки:


Процедура ОбойтиРеквизитыШапки(ОбъектБД)
    
    МетаданныеОбъекта = ОбъектБД.Метаданные();
    
    Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
        ИмяРеквизита = Реквизит.Имя;
        ЗначениеРеквизита = ОбъектБД[ИмяРеквизита];
        Сообщить(ИмяРеквизита + " = " + ЗначениеРеквизита);
    КонецЦикла;
    
КонецПроцедуры

Комплексный обход: Реквизиты шапки + Табличные части

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


Процедура ПолныйОбходОбъекта(ОбъектБД)
    
    МетаданныеОбъекта = ОбъектБД.Метаданные();
    
    // 1. Выводим реквизиты шапки
    Сообщить("=== Реквизиты шапки ===");
    Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
        Сообщить(Реквизит.Имя + " = " + ОбъектБД[Реквизит.Имя]);
    КонецЦикла;
    
    // 2. Обходим табличные части
    Сообщить("=== Табличные части ===");
    Для Каждого ТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
        Сообщить("Табличная часть: " + ТЧ.Имя);
        
        // Получаем коллекцию строк конкретной табличной части
        ТабличнаяЧастьОбъекта = ОбъектБД[ТЧ.Имя];
        
        НомерСтроки = 1;
        Для Каждого СтрокаТЧ Из ТабличнаяЧастьОбъекта Цикл
            Сообщить("  Строка № " + НомерСтроки);
            
            // Обходим реквизиты внутри строки табличной части
            Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
                Сообщить("    " + РеквизитТЧ.Имя + " = " + СтрокаТЧ[РеквизитТЧ.Имя]);
            КонецЦикла;
            
            НомерСтроки = НомерСтроки + 1;
        КонецЦикла;
    EndЦикла;
    
КонецПроцедуры

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

Работа со стандартными и общими реквизитами

На форумах часто встречается ошибочное утверждение, что общие реквизиты недоступны через метаданные конкретного объекта. Проанализируем эту ситуацию глубже.

Стандартные реквизиты (такие как Ссылка, Код, Наименование, Проведен, Дата, Номер) хранятся в отдельной коллекции метаданных. Если вам нужно обойти вообще все поля объекта, обязательно включайте обход коллекции СтандартныеРеквизиты:


Для Каждого СтандартныйРеквизит Из МетаданныеОбъекта.СтандартныеРеквизиты Цикл
    Сообщить(СтандартныйРеквизит.Имя + " = " + ОбъектБД[СтандартныйРеквизит.Имя]);
КонецЦикла;

Общие реквизиты также проецируются на свойства объекта, если они включены для данного типа метаданных. К ним можно свободно обращаться через квадратные скобки: ОбъектБД[ИмяОбщегоРеквизита]. Чтобы динамически определить, какие общие реквизиты относятся к вашему объекту, используйте следующий алгоритм:


Для Каждого ОбщийРеквизит Из Метаданные.ОбщиеРеквизиты Цикл
    СоставЭлемента = ОбщийРеквизит.Состав.Найти(МетаданныеОбъекта);
    Если СоставЭлемента <> Неопределено Тогда
        // Проверяем режим использования общего реквизита для нашего объекта
        Используется = Ложь;
        Если СоставЭлемента.Использование = ОбходМетаданныхИспользованияОбщегоРеквизита.Использовать Тогда
            Используется = Истина;
        ИначеЕсли СоставЭлемента.Использование = ОбходМетаданныхИспользованияОбщегоРеквизита.Автоматически Тогда
            Используется = (ОбщийРеквизит.Автоиспользование = АвтоиспользованиеОбщегоРеквизита.Использовать);
        КонецЕсли;
        
        Если Используется Тогда
            Сообщить("Общий реквизит: " + ОбщийРеквизит.Имя + " = " + ОбъектБД[ОбщийРеквизит.Имя]);
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

Проблема производительности: обход реквизитов по ссылке

Если в качестве параметра ОбъектБД выступает ссылка (например, ДокументСсылка или СправочникСсылка), то обращение к реквизитам через конструкцию Ссылка[ИмяРеквизита] в цикле вызовет критическое падение производительности. Платформа 1С при обращении к каждому новому реквизиту ссылки будет совершать отдельный неявный запрос к базе данных для считывания всей таблицы объекта (если данные не кэшированы). Это порождает проблему "N+1 запросов".

Как правильно: Если на входе у вас ссылка, наилучшим решением будет динамическое построение текста запроса. Мы соберем все имена реквизитов в одну строку через запятую, выполним один легкий запрос к СУБД и получим результат за одно обращение.


Функция ПолучитьВсеДанныеСсылкиБыстро(СсылкаНаОбъект) Экспорт
    
    МетаданныеОбъекта = СсылкаНаОбъект.Метаданные();
    МассивИмен = Новый Массив;
    
    // Собираем имена обычных реквизитов
    Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
        МассивИмен.Добавить(Реквизит.Имя);
    КонецЦикла;
    
    // Собираем имена стандартных реквизитов
    Для Каждого СтандартныйРеквизит Из МетаданныеОбъекта.StandardAttributes Цикл
        МассивИмен.Добавить(СтандартныйРеквизит.Имя);
    КонецЦикла;
    
    Если МассивИмен.Количество() = 0 Тогда
        Возврат Неопределено;
    КонецЕсли;
    
    // Формируем текст запроса динамически
    СтрокаПолей = СтрСоединить(МассивИмен, ", ");
    ТекстЗапроса = "ВЫБРАТЬ " + СтрокаПолей + " ИЗ " + МетаданныеОбъекта.ПолноеИмя() + " ГДЕ Ссылка = &Ссылка";
    
    Запрос = Новый Запрос(ТекстЗапроса);
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        ДанныеОбъекта = Новый Структура;
        Для Каждого ИмяПоля Из МассивИмен Цикл
            ДанныеОбъекта.Вставить(ИмяПоля, Выборка[ИмяПоля]);
        КонецЦикла;
        Возврат ДанныеОбъекта;
    КонецЕсли;
    
    Возврат Неопределено;
КонецФункции

Альтернативные методы платформы и БСП

Рассмотрим полезные альтернативы ручному написанию циклов по метаданным, которые значительно экономят время разработки:

  1. Быстрое копирование реквизитов. Если ваша цель — скопировать значения из одного объекта в другой (или в структуру с аналогичными именами полей), используйте метод платформы ЗаполнитьЗначенияСвойств(Приемник, Источник). Он написан на C++ на уровне ядра платформы и выполняет операцию сопоставления имен реквизитов мгновенно.
  2. Быстрая проверка существования реквизита. Если вы используете Библиотеку Стандартных Подсистем (БСП), вам не нужно писать циклы поиска. Достаточно вызвать метод: ОбщегоНазначения.ЕстьРеквизитОбъекта(ИмяРеквизита, МетаданныеОбъекта) или универсальный метод проверки наличия свойства у любого объекта (включая структуры и строки ТЧ): ОбщегоНазначенияКлиентСервер.ЕстьРеквизитИлиСвойствоОбъекта(Объект, ИмяРеквизита).
  3. Создание полного слепка (дампа) данных. Если вам необходимо сериализовать весь объект со всеми его табличными частями и реквизитами для выгрузки или логирования, используйте XDTO-сериализацию. Метод СериализаторXDTO.ЗаписатьXDTO(Объект) автоматически соберет все данные в единую XML-структуру без необходимости написания сложных вложенных циклов.
← На главную