Обход всех метаданных конфигурации 1С во встроенном языке
Содержание
Перебрать все метаданные в 1С 8.3 можно через глобальный объект Метаданные и его коллекции: Метаданные.Справочники, Метаданные.Документы, Метаданные.РегистрыСведений и так далее. Каждую коллекцию обходят циклом Для Каждого ... Из ... Цикл, а у каждого объекта читают имя, синоним, реквизиты и табличные части.
Если нужен единый плоский список всех объектов конфигурации — перебирают известные виды через индекс Метаданные[ВидОбъекта]. Если нужно спуститься вглубь, к реквизитам и строкам табличных частей, — добавляют рекурсию. Ниже — рабочий код для каждой задачи, фильтрация по свойствам, защита от ошибок и готовая схема универсальной обработки.
Код выполняйте на сервере (&НаСервере) или в толстом клиенте. В управляемом приложении объект Метаданные на тонком клиенте доступен не полностью — работу с конфигурацией выносят на сервер.
Глобальный объект «Метаданные» и его коллекции
Метаданные — это дерево описания конфигурации, которое платформа держит в оперативной памяти. Через него нельзя менять данные, но можно прочитать всё о структуре: какие есть справочники, какие у них реквизиты, типы, формы, макеты.
Верхний уровень — это коллекции по видам объектов. Обойти один вид:
Для Каждого ОбъектМД Из Метаданные.Справочники Цикл
Сообщить(ОбъектМД.Имя + " — " + ОбъектМД.Синоним);
КонецЦикла;
У каждого объекта метаданных есть базовые свойства, которые пригодятся при любом обходе:
- 🔹
Имя— внутреннее имя (Контрагенты); - 🔹
Синоним— представление для пользователя (Контрагенты); - 🔹
ПолноеИмя()— имя с видом через точку (Справочник.Контрагенты); - 🔹
Комментарий— комментарий разработчика; - 🔹
Реквизиты,ТабличныеЧасти,Формы,Макеты,Команды— вложенные коллекции.
ПолноеИмя() — это метод (со скобками), а Имя — свойство. Полное имя удобно тем, что его можно подставить прямо в текст запроса как имя таблицы.
Набор вложенных коллекций зависит от вида объекта — именно поэтому универсальный обход приходится строить аккуратно. Вот что хранят самые частые виды:
| Коллекция объекта | Вложенные коллекции | Хранит данные в базе |
|---|---|---|
Справочники | Реквизиты, ТабличныеЧасти | да |
Документы | Реквизиты, ТабличныеЧасти | да |
РегистрыСведений | Измерения, Ресурсы, Реквизиты | да |
РегистрыНакопления | Измерения, Ресурсы, Реквизиты | да |
Перечисления | — (значения фиксированы) | да |
Обработки, Отчеты | Реквизиты, Формы, Команды | нет |
Последняя колонка решает, можно ли подставить полное имя объекта в запрос. Для обработок и отчётов таблиц в базе нет — это учитывают на шаге чтения данных.
Как собрать единый плоский список объектов
Отдельной коллекции «все объекты сразу» в платформе нет. Поэтому плоский список собирают, перебирая известные виды. Имена видов перечисляют строкой и обращаются к коллекции по индексу Метаданные[ВидОбъекта]:
ВидыОбъектов = "Константы,Справочники,Документы,Перечисления,"
+ "ПланыВидовХарактеристик,ПланыСчетов,ПланыВидовРасчета,"
+ "РегистрыСведений,РегистрыНакопления,РегистрыБухгалтерии,РегистрыРасчета,"
+ "БизнесПроцессы,Задачи,ПланыОбмена,Отчеты,Обработки";
ПлоскийСписок = Новый Массив;
Для Каждого Вид Из СтрРазделить(ВидыОбъектов, ",", Ложь) Цикл
Для Каждого ОбъектМД Из Метаданные[Вид] Цикл
ПлоскийСписок.Добавить(ОбъектМД.ПолноеИмя());
КонецЦикла;
КонецЦикла;
Сообщить(СтрШаблон("Всего объектов: %1", ПлоскийСписок.Количество()));
Обращение Метаданные["Справочники"] равнозначно Метаданные.Справочники — это тот же механизм доступа по имени, что используют стандартные обработки выгрузки данных.
Список видов в примере — основной набор прикладных объектов. При необходимости добавляют ОбщиеМодули, ОбщиеФормы, ПланыОбмена, РегламентныеЗадания, ОпределяемыеТипы. Полный перечень видов есть в синтакс-помощнике по объекту Метаданные; выдумывать имена не нужно — используйте только те, что там указаны.
Не все виды объектов хранят данные. У Обработки и Отчеты таблиц в базе нет — их полное имя нельзя подставить в запрос. Это важно учесть на шаге с чтением данных.
Рекурсивный обход: реквизиты, табличные части, измерения
Конфигурация — не однородное дерево: у разных видов разный набор вложенных коллекций. У справочника и документа есть Реквизиты и ТабличныеЧасти, у регистра — Измерения, Ресурсы и Реквизиты, а у перечисления реквизитов нет вовсе. Поэтому «рекурсия» на практике — это функция, которая для каждого объекта обходит именно его вложенные коллекции.
Табличная часть сама содержит реквизиты — вот где появляется второй уровень вложенности:
Процедура ВывестиСтруктуру(ОбъектМД)
Сообщить(ОбъектМД.ПолноеИмя());
Для Каждого Реквизит Из ОбъектМД.Реквизиты Цикл
Сообщить(" " + Реквизит.Имя + " : " + Реквизит.Тип);
КонецЦикла;
Для Каждого ТЧ Из ОбъектМД.ТабличныеЧасти Цикл
Сообщить(" [ТЧ] " + ТЧ.Имя);
Для Каждого Реквизит Из ТЧ.Реквизиты Цикл
Сообщить(" " + Реквизит.Имя + " : " + Реквизит.Тип);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Этот код корректен для справочников и документов — у них обе коллекции есть всегда. Для регистров добавьте обход Измерения и Ресурсы. А чтобы написать по-настоящему универсальную процедуру под любой вид объекта, доступ к коллекции заворачивают в Попытка, потому что обращение к отсутствующей коллекции (например, Перечисление.Реквизиты) вызовет ошибку. Этот приём разобран в следующем разделе.
Фильтрация объектов и чтение их свойств
Полный перебор редко нужен целиком — обычно ищут подмножество. Два рабочих способа отбора.
По части имени. Когда нужны объекты, в названии которых есть слово:
Для Каждого ИмяОбъекта Из ПлоскийСписок Цикл
Если СтрНайти(ВРег(ИмяОбъекта), ВРег("Контрагент")) > 0 Тогда
Сообщить(ИмяОбъекта);
КонецЕсли;
КонецЦикла;
По типу. Когда известен тип и нужно найти объект метаданных, которому он принадлежит, — метод НайтиПоТипу:
ТипСсылки = Тип("СправочникСсылка.Контрагенты");
ОбъектМД = Метаданные.НайтиПоТипу(ТипСсылки);
Если ОбъектМД <> Неопределено Тогда
Сообщить(ОбъектМД.ПолноеИмя());
КонецЕсли;
Обратная операция — получить объект по его строковому полному имени — делается методом НайтиПоПолномуИмени:
ОбъектМД = Метаданные.НайтиПоПолномуИмени("Справочник.Номенклатура");
Оба метода возвращают Неопределено, если объект не найден, — проверка на Неопределено обязательна, иначе дальше получите ошибку обращения к несуществующему объекту.
Чтобы разобрать типы реквизита, читают свойство Тип — это объект ОписаниеТипов, у которого есть метод Типы(), возвращающий массив всех допустимых типов поля:
ОбъектМД = Метаданные.Справочники.Контрагенты;
Для Каждого Реквизит Из ОбъектМД.Реквизиты Цикл
КоличествоТипов = Реквизит.Тип.Типы().Количество();
Сообщить(СтрШаблон("%1 — типов: %2", Реквизит.Имя, КоличествоТипов));
КонецЦикла;
Так находят составные реквизиты (КоличествоТипов больше одного) — частый повод для аудита конфигурации.
Коротко о том, какой способ доступа выбрать под задачу:
| Способ | Когда применять | Что возвращает |
|---|---|---|
Для Каждого ... Из Метаданные.Справочники | нужны все объекты одного вида | объекты метаданных вида |
Метаданные[ВидОбъекта] | обход сразу нескольких видов в цикле | коллекцию вида по имени |
Метаданные.НайтиПоТипу(Тип) | известен тип, нужен его объект | объект или Неопределено |
Метаданные.НайтиПоПолномуИмени(Строка) | известно полное имя строкой | объект или Неопределено |
Защита от ошибок при обходе
Универсальный обход ломается на краевых случаях. Три из них встречаются чаще всего.
Отсутствующая коллекция. У перечисления нет реквизитов, у константы нет табличных частей. Прямое обращение вызовет ошибку «Поле объекта не обнаружено». Обход защищают Попытка:
Попытка
Реквизиты = ОбъектМД.Реквизиты;
Исключение
Реквизиты = Неопределено;
КонецПопытки;
Если Реквизиты <> Неопределено Тогда
Для Каждого Реквизит Из Реквизиты Цикл
// обработка реквизита
КонецЦикла;
КонецЕсли;
Объект без таблицы данных. Если по ходу обхода вы ещё и читаете данные через запрос, помните: у обработок и отчётов таблиц нет, а константы читаются через менеджер, а не запросом по полному имени. Подставлять ОбъектМД.ПолноеИмя() в текст запроса можно только для объектов, которые физически хранят записи (справочники, документы, регистры, планы счетов и видов характеристик).
Права доступа . Под ограниченными правами часть объектов читать нельзя. Проверяйте право перед обращением к данным:
Если ПравоДоступа("Чтение", ОбъектМД) Тогда
// объект доступен текущему пользователю
КонецЕсли;
Если обход выполняет запросы к данным, добавляйте в текст запроса РАЗРЕШЕННЫЕ. Иначе на объекте с ограничением доступа на уровне записей (RLS) запрос завершится ошибкой.
Универсальная обработка для анализа конфигурации
Разовый вывод в окно сообщений годится для отладки. Для постоянной работы обход оформляют внешней обработкой (.epf) с формой: результат собирают в таблицу или дерево значений и показывают пользователю.
Серверная функция, которая собирает сводку по всем объектам, выглядит так:
&НаСервереБезКонтекста
Функция СобратьСводкуМетаданных()
Результат = Новый ТаблицаЗначений;
Результат.Колонки.Добавить("ПолноеИмя");
Результат.Колонки.Добавить("Вид");
Результат.Колонки.Добавить("КоличествоРеквизитов", Новый ОписаниеТипов("Число"));
ВидыОбъектов = "Справочники,Документы,РегистрыСведений,РегистрыНакопления";
Для Каждого Вид Из СтрРазделить(ВидыОбъектов, ",", Ложь) Цикл
Для Каждого ОбъектМД Из Метаданные[Вид] Цикл
Строка = Результат.Добавить();
Строка.ПолноеИмя = ОбъектМД.ПолноеИмя();
Строка.Вид = Вид;
Попытка
Строка.КоличествоРеквизитов = ОбъектМД.Реквизиты.Количество();
Исключение
Строка.КоличествоРеквизитов = 0;
КонецПопытки;
КонецЦикла;
КонецЦикла;
Возврат Результат;
КонецФункции
Готовую таблицу выводят в элемент формы типа «Таблица значений» или переносят в реквизит-дерево через ЗначениеВРеквизитФормы. Дальше по ней делают всё, ради чего и затевается обход: ищут объекты без реквизитов, собирают список таблиц для выгрузки, строят отчёт о структуре базы.
Частый практический сценарий — посчитать число записей в каждой таблице. Полное имя объекта подставляется в запрос как имя таблицы, а Попытка отсекает объекты, у которых таблицы нет:
ВидыОбъектов = "Справочники,Документы,РегистрыСведений,РегистрыНакопления";
Для Каждого Вид Из СтрРазделить(ВидыОбъектов, ",", Ложь) Цикл
Для Каждого ОбъектМД Из Метаданные[Вид] Цикл
Если НЕ ПравоДоступа("Чтение", ОбъектМД) Тогда
Продолжить;
КонецЕсли;
Попытка
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ КОЛИЧЕСТВО(*) КАК Всего
|ИЗ " + ОбъектМД.ПолноеИмя();
Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
Сообщить(СтрШаблон("%1 — записей: %2", ОбъектМД.ПолноеИмя(), Выборка.Всего));
Исключение
// объект не хранит данные или недоступен — пропускаем
КонецПопытки;
КонецЦикла;
КонецЦикла;
КОЛИЧЕСТВО(*) считает строки на стороне СУБД и не тянет данные в память — поэтому такой обход остаётся быстрым даже на крупной базе.
Три типовые задачи, которые закрывает такая обработка:
- Инвентаризация. Список всех объектов с числом реквизитов и табличных частей — для оценки объёма конфигурации.
- Поиск использования типа. По
НайтиПоТипунаходят, где применяется конкретный справочник. - Массовая выгрузка. Полные имена подставляют в запросы и выгружают данные всех таблиц подряд.
Скорость на крупных конфигурациях
Сам перебор метаданных — быстрая операция. Платформа читает дерево конфигурации в память один раз при старте, поэтому обход имён и свойств идёт из оперативной памяти. Даже для конфигурации уровня ERP, где несколько тысяч объектов метаданных, перебор полного списка имён занимает доли секунды.
Тормозит не обход структуры, а чтение данных. Если на каждом объекте вы выполняете запрос, время растёт пропорционально числу таблиц и объёму записей. Отсюда правила:
- ⚡ Считайте, а не выгружайте. Если нужно только число записей —
ВЫБРАТЬ КОЛИЧЕСТВО(*), а не выборка всех строк с последующим.Количество(). - ⚡ Не дёргайте поиск в цикле.
НайтиПоПолномуИменииНайтиПоТипувнутри вложенного цикла заметно замедляют обход. Соберите нужные объекты один раз в массив или соответствие и работайте с ним. - ⚡ Выводите пачкой.
Сообщитьна каждую из тысяч строк подвешивает интерфейс. Накапливайте результат в таблице значений и показывайте целиком. - ⚡ Читайте порциями. Большие таблицы перебирайте выборкой
Запрос.Выполнить().Выбрать(), а не выгрузкой всей таблицы в память.
Кэшировать результат обхода структуры обычно не требуется — он и так мгновенный. А вот собранный плоский список объектов, если он нужен многократно за один сеанс, стоит сохранить в переменную и переиспользовать, а не пересобирать.
Можно ли обойти метаданные на тонком клиенте без обращения к серверу?
Метаданные на тонком клиенте недоступна. Работу с конфигурацией выносят в серверную процедуру (&НаСервере или &НаСервереБезКонтекста), а на клиент возвращают уже готовый результат.Как получить менеджер объекта по его имени из метаданных?
Справочники[ОбъектМД.Имя] вернёт менеджер справочника, Документы[ОбъектМД.Имя] — менеджер документа. Дальше у менеджера доступны методы создания элементов и выборок.Почему запрос по полному имени объекта падает с ошибкой?
РАЗРЕШЕННЫЕ в текст запроса.Чем отличается `НайтиПоТипу` от `НайтиПоПолномуИмени`?
НайтиПоТипу принимает значение типа (например, Тип("СправочникСсылка.Контрагенты")) и ищет объект по типу его ссылки. НайтиПоПолномуИмени принимает строку вида Справочник.Контрагенты. Оба возвращают Неопределено, если ничего не нашли.Как найти все составные реквизиты в конфигурации?
Реквизит.Тип.Типы().Количество() > 1. Это и есть признак составного типа — поля, которое может хранить ссылки на несколько видов объектов.