Обход всех метаданных конфигурации 1С во встроенном языке

· 9 мин

Содержание

Перебрать все метаданные в 1С 8.3 можно через глобальный объект Метаданные и его коллекции: Метаданные.Справочники, Метаданные.Документы, Метаданные.РегистрыСведений и так далее. Каждую коллекцию обходят циклом Для Каждого ... Из ... Цикл, а у каждого объекта читают имя, синоним, реквизиты и табличные части.

Если нужен единый плоский список всех объектов конфигурации — перебирают известные виды через индекс Метаданные[ВидОбъекта]. Если нужно спуститься вглубь, к реквизитам и строкам табличных частей, — добавляют рекурсию. Ниже — рабочий код для каждой задачи, фильтрация по свойствам, защита от ошибок и готовая схема универсальной обработки.

Код выполняйте на сервере (&НаСервере) или в толстом клиенте. В управляемом приложении объект Метаданные на тонком клиенте доступен не полностью — работу с конфигурацией выносят на сервер.

Глобальный объект «Метаданные» и его коллекции

Метаданные — это дерево описания конфигурации, которое платформа держит в оперативной памяти. Через него нельзя менять данные, но можно прочитать всё о структуре: какие есть справочники, какие у них реквизиты, типы, формы, макеты.

Верхний уровень — это коллекции по видам объектов. Обойти один вид:

Для Каждого ОбъектМД Из Метаданные.Справочники Цикл
    Сообщить(ОбъектМД.Имя + " — " + ОбъектМД.Синоним);
КонецЦикла;

У каждого объекта метаданных есть базовые свойства, которые пригодятся при любом обходе:

  • 🔹 Имя — внутреннее имя (Контрагенты);
  • 🔹 Синоним — представление для пользователя (Контрагенты);
  • 🔹 ПолноеИмя() — имя с видом через точку (Справочник.Контрагенты);
  • 🔹 Комментарий — комментарий разработчика;
  • 🔹 Реквизиты, ТабличныеЧасти, Формы, Макеты, Команды — вложенные коллекции.

ПолноеИмя() — это метод (со скобками), а Имя — свойство. Полное имя удобно тем, что его можно подставить прямо в текст запроса как имя таблицы.

Набор вложенных коллекций зависит от вида объекта — именно поэтому универсальный обход приходится строить аккуратно. Вот что хранят самые частые виды:

Коллекция объектаВложенные коллекцииХранит данные в базе
СправочникиРеквизиты, ТабличныеЧастида
ДокументыРеквизиты, ТабличныеЧастида
РегистрыСведенийИзмерения, Ресурсы, Реквизитыда
РегистрыНакопленияИзмерения, Ресурсы, Реквизитыда
Перечисления— (значения фиксированы)да
Обработки, ОтчетыРеквизиты, Формы, Командынет

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

Как собрать единый плоский список объектов

Отдельной коллекции «все объекты сразу» в платформе нет. Поэтому плоский список собирают, перебирая известные виды. Имена видов перечисляют строкой и обращаются к коллекции по индексу Метаданные[ВидОбъекта]:

ВидыОбъектов = "Константы,Справочники,Документы,Перечисления,"
    + "ПланыВидовХарактеристик,ПланыСчетов,ПланыВидовРасчета,"
    + "РегистрыСведений,РегистрыНакопления,РегистрыБухгалтерии,РегистрыРасчета,"
    + "БизнесПроцессы,Задачи,ПланыОбмена,Отчеты,Обработки";

ПлоскийСписок = Новый Массив;
Для Каждого Вид Из СтрРазделить(ВидыОбъектов, ",", Ложь) Цикл
    Для Каждого ОбъектМД Из Метаданные[Вид] Цикл
        ПлоскийСписок.Добавить(ОбъектМД.ПолноеИмя());
    КонецЦикла;
КонецЦикла;

Сообщить(СтрШаблон("Всего объектов: %1", ПлоскийСписок.Количество()));

Обращение Метаданные["Справочники"] равнозначно Метаданные.Справочники — это тот же механизм доступа по имени, что используют стандартные обработки выгрузки данных.

Список видов в примере — основной набор прикладных объектов. При необходимости добавляют ОбщиеМодули, ОбщиеФормы, ПланыОбмена, РегламентныеЗадания, ОпределяемыеТипы. Полный перечень видов есть в синтакс-помощнике по объекту Метаданные; выдумывать имена не нужно — используйте только те, что там указаны.

Не все виды объектов хранят данные. У Обработки и Отчеты таблиц в базе нет — их полное имя нельзя подставить в запрос. Это важно учесть на шаге с чтением данных.

Рекурсивный обход: реквизиты, табличные части, измерения

Конфигурация — не однородное дерево: у разных видов разный набор вложенных коллекций. У справочника и документа есть Реквизиты и ТабличныеЧасти, у регистра — Измерения, Ресурсы и Реквизиты, а у перечисления реквизитов нет вовсе. Поэтому «рекурсия» на практике — это функция, которая для каждого объекта обходит именно его вложенные коллекции.

Табличная часть сама содержит реквизиты — вот где появляется второй уровень вложенности:

Процедура ВывестиСтруктуру(ОбъектМД)
    Сообщить(ОбъектМД.ПолноеИмя());

    Для Каждого Реквизит Из ОбъектМД.Реквизиты Цикл
        Сообщить("  " + Реквизит.Имя + " : " + Реквизит.Тип);
    КонецЦикла;

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

Этот код корректен для справочников и документов — у них обе коллекции есть всегда. Для регистров добавьте обход Измерения и Ресурсы. А чтобы написать по-настоящему универсальную процедуру под любой вид объекта, доступ к коллекции заворачивают в Попытка, потому что обращение к отсутствующей коллекции (например, Перечисление.Реквизиты) вызовет ошибку. Этот приём разобран в следующем разделе.

Фильтрация объектов и чтение их свойств

Полный перебор редко нужен целиком — обычно ищут подмножество. Два рабочих способа отбора.

По части имени. Когда нужны объекты, в названии которых есть слово:

Для Каждого ИмяОбъекта Из ПлоскийСписок Цикл
    Если СтрНайти(ВРег(ИмяОбъекта), ВРег("Контрагент")) > 0 Тогда
        Сообщить(ИмяОбъекта);
    КонецЕсли;
КонецЦикла;

По типу. Когда известен тип и нужно найти объект метаданных, которому он принадлежит, — метод НайтиПоТипу:

ТипСсылки = Тип("СправочникСсылка.Контрагенты");
ОбъектМД = Метаданные.НайтиПоТипу(ТипСсылки);
Если ОбъектМД <> Неопределено Тогда
    Сообщить(ОбъектМД.ПолноеИмя());
КонецЕсли;

Обратная операция — получить объект по его строковому полному имени — делается методом НайтиПоПолномуИмени:

ОбъектМД = Метаданные.НайтиПоПолномуИмени("Справочник.Номенклатура");

Оба метода возвращают Неопределено, если объект не найден, — проверка на Неопределено обязательна, иначе дальше получите ошибку обращения к несуществующему объекту.

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

ОбъектМД = Метаданные.Справочники.Контрагенты;
Для Каждого Реквизит Из ОбъектМД.Реквизиты Цикл
    КоличествоТипов = Реквизит.Тип.Типы().Количество();
    Сообщить(СтрШаблон("%1 — типов: %2", Реквизит.Имя, КоличествоТипов));
КонецЦикла;

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

Коротко о том, какой способ доступа выбрать под задачу:

СпособКогда применятьЧто возвращает
Для Каждого ... Из Метаданные.Справочникинужны все объекты одного видаобъекты метаданных вида
Метаданные[ВидОбъекта]обход сразу нескольких видов в циклеколлекцию вида по имени
Метаданные.НайтиПоТипу(Тип)известен тип, нужен его объектобъект или Неопределено
Метаданные.НайтиПоПолномуИмени(Строка)известно полное имя строкойобъект или Неопределено

Защита от ошибок при обходе

Универсальный обход ломается на краевых случаях. Три из них встречаются чаще всего.

Отсутствующая коллекция. У перечисления нет реквизитов, у константы нет табличных частей. Прямое обращение вызовет ошибку «Поле объекта не обнаружено». Обход защищают Попытка:

Попытка
    Реквизиты = ОбъектМД.Реквизиты;
Исключение
    Реквизиты = Неопределено;
КонецПопытки;

Если Реквизиты <> Неопределено Тогда
    Для Каждого Реквизит Из Реквизиты Цикл
        // обработка реквизита
    КонецЦикла;
КонецЕсли;

Объект без таблицы данных. Если по ходу обхода вы ещё и читаете данные через запрос, помните: у обработок и отчётов таблиц нет, а константы читаются через менеджер, а не запросом по полному имени. Подставлять ОбъектМД.ПолноеИмя() в текст запроса можно только для объектов, которые физически хранят записи (справочники, документы, регистры, планы счетов и видов характеристик).

Права доступа . Под ограниченными правами часть объектов читать нельзя. Проверяйте право перед обращением к данным:

Если ПравоДоступа("Чтение", ОбъектМД) Тогда
    // объект доступен текущему пользователю
КонецЕсли;

Если обход выполняет запросы к данным, добавляйте в текст запроса РАЗРЕШЕННЫЕ. Иначе на объекте с ограничением доступа на уровне записей (RLS) запрос завершится ошибкой.

Универсальная обработка для анализа конфигурации

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

Серверная функция, которая собирает сводку по всем объектам, выглядит так:

&НаСервереБезКонтекста
Функция СобратьСводкуМетаданных()
    Результат = Новый ТаблицаЗначений;
    Результат.Колонки.Добавить("ПолноеИмя");
    Результат.Колонки.Добавить("Вид");
    Результат.Колонки.Добавить("КоличествоРеквизитов", Новый ОписаниеТипов("Число"));

    ВидыОбъектов = "Справочники,Документы,РегистрыСведений,РегистрыНакопления";
    Для Каждого Вид Из СтрРазделить(ВидыОбъектов, ",", Ложь) Цикл
        Для Каждого ОбъектМД Из Метаданные[Вид] Цикл
            Строка = Результат.Добавить();
            Строка.ПолноеИмя = ОбъектМД.ПолноеИмя();
            Строка.Вид = Вид;
            Попытка
                Строка.КоличествоРеквизитов = ОбъектМД.Реквизиты.Количество();
            Исключение
                Строка.КоличествоРеквизитов = 0;
            КонецПопытки;
        КонецЦикла;
    КонецЦикла;

    Возврат Результат;
КонецФункции

Готовую таблицу выводят в элемент формы типа «Таблица значений» или переносят в реквизит-дерево через ЗначениеВРеквизитФормы. Дальше по ней делают всё, ради чего и затевается обход: ищут объекты без реквизитов, собирают список таблиц для выгрузки, строят отчёт о структуре базы.

Частый практический сценарий — посчитать число записей в каждой таблице. Полное имя объекта подставляется в запрос как имя таблицы, а Попытка отсекает объекты, у которых таблицы нет:

ВидыОбъектов = "Справочники,Документы,РегистрыСведений,РегистрыНакопления";
Для Каждого Вид Из СтрРазделить(ВидыОбъектов, ",", Ложь) Цикл
    Для Каждого ОбъектМД Из Метаданные[Вид] Цикл
        Если НЕ ПравоДоступа("Чтение", ОбъектМД) Тогда
            Продолжить;
        КонецЕсли;
        Попытка
            Запрос = Новый Запрос;
            Запрос.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ КОЛИЧЕСТВО(*) КАК Всего
                           |ИЗ " + ОбъектМД.ПолноеИмя();
            Выборка = Запрос.Выполнить().Выбрать();
            Выборка.Следующий();
            Сообщить(СтрШаблон("%1 — записей: %2", ОбъектМД.ПолноеИмя(), Выборка.Всего));
        Исключение
            // объект не хранит данные или недоступен — пропускаем
        КонецПопытки;
    КонецЦикла;
КонецЦикла;

КОЛИЧЕСТВО(*) считает строки на стороне СУБД и не тянет данные в память — поэтому такой обход остаётся быстрым даже на крупной базе.

Три типовые задачи, которые закрывает такая обработка:

  1. Инвентаризация. Список всех объектов с числом реквизитов и табличных частей — для оценки объёма конфигурации.
  2. Поиск использования типа. По НайтиПоТипу находят, где применяется конкретный справочник.
  3. Массовая выгрузка. Полные имена подставляют в запросы и выгружают данные всех таблиц подряд.
Перед запуском обхода на рабочей базе проверьте
Выполнено: 0 / 5

Скорость на крупных конфигурациях

Сам перебор метаданных — быстрая операция. Платформа читает дерево конфигурации в память один раз при старте, поэтому обход имён и свойств идёт из оперативной памяти. Даже для конфигурации уровня ERP, где несколько тысяч объектов метаданных, перебор полного списка имён занимает доли секунды.

Тормозит не обход структуры, а чтение данных. Если на каждом объекте вы выполняете запрос, время растёт пропорционально числу таблиц и объёму записей. Отсюда правила:

  • ⚡ Считайте, а не выгружайте. Если нужно только число записей — ВЫБРАТЬ КОЛИЧЕСТВО(*), а не выборка всех строк с последующим .Количество().
  • ⚡ Не дёргайте поиск в цикле. НайтиПоПолномуИмени и НайтиПоТипу внутри вложенного цикла заметно замедляют обход. Соберите нужные объекты один раз в массив или соответствие и работайте с ним.
  • ⚡ Выводите пачкой. Сообщить на каждую из тысяч строк подвешивает интерфейс. Накапливайте результат в таблице значений и показывайте целиком.
  • ⚡ Читайте порциями. Большие таблицы перебирайте выборкой Запрос.Выполнить().Выбрать(), а не выгрузкой всей таблицы в память.

Кэшировать результат обхода структуры обычно не требуется — он и так мгновенный. А вот собранный плоский список объектов, если он нужен многократно за один сеанс, стоит сохранить в переменную и переиспользовать, а не пересобирать.

Можно ли обойти метаданные на тонком клиенте без обращения к серверу?
Полноценно — нет. Часть свойств объекта Метаданные на тонком клиенте недоступна. Работу с конфигурацией выносят в серверную процедуру (&НаСервере или &НаСервереБезКонтекста), а на клиент возвращают уже готовый результат.
Как получить менеджер объекта по его имени из метаданных?
Через индекс менеджера вида: Справочники[ОбъектМД.Имя] вернёт менеджер справочника, Документы[ОбъектМД.Имя] — менеджер документа. Дальше у менеджера доступны методы создания элементов и выборок.
Почему запрос по полному имени объекта падает с ошибкой?
Либо объект не хранит данные (обработка, отчёт, константа), либо у текущего пользователя нет прав на чтение. Проверьте вид объекта и добавьте РАЗРЕШЕННЫЕ в текст запроса.
Чем отличается `НайтиПоТипу` от `НайтиПоПолномуИмени`?
НайтиПоТипу принимает значение типа (например, Тип("СправочникСсылка.Контрагенты")) и ищет объект по типу его ссылки. НайтиПоПолномуИмени принимает строку вида Справочник.Контрагенты. Оба возвращают Неопределено, если ничего не нашли.
Как найти все составные реквизиты в конфигурации?
Переберите реквизиты объектов и проверьте Реквизит.Тип.Типы().Количество() > 1. Это и есть признак составного типа — поля, которое может хранить ссылки на несколько видов объектов.