В процессе разработки универсальных инструментов, выгрузок или обработок администрирования перед программистом 1С часто возникают две взаимосвязанные задачи. Первая — получить полный список доступных типов данных (аналогично подсказке в конфигураторе при вводе конструкции Тип("...")). Вторая — выполнить универсальный поиск существующего объекта в базе данных, зная только его уникальный идентификатор (GUID) и не имея информации о его типе.
В этой статье мы подробно рассмотрим, как программно обходить метаданные для построения списка типов, разберем базовый алгоритм поиска по GUID, проанализируем его слабые стороны с точки зрения производительности и предложим высокоэффективные варианты оптимизации, включая использование механизмов БСП и динамическую сборку пакетных запросов.
При вводе строки в конфигураторе внутри конструкции Тип("...") платформа выводит интерактивную подсказку. Строковое представление типа для ссылочных объектов метаданных в 1С всегда формируется по строгому системному шаблону: [ИмяКлассаМетаданных]Ссылка.[ИмяОбъекта] (например, СправочникСсылка.Номенклатура или ДокументСсылка.ЗаказКлиента).
Вопреки распространенному заблуждению, коллекции метаданных в платформе 1С «Предприятие» можно обходить стандартным циклом Для Каждого. Чтобы собрать полный список строковых представлений типов для всех ссылочных объектов конфигурации, мы можем использовать следующий алгоритм:
СписокТипов = Новый Массив;
// Определим структуру классов метаданных, которые имеют ссылочный тип
КлассыСсылок = Новый Структура;
КлассыСсылок.Вставить("Справочники", "СправочникСсылка");
КлассыСсылок.Вставить("Документы", "ДокументСсылка");
КлассыСсылок.Вставить("ПланыВидовХарактеристик", "ПланВидовХарактеристикСсылка");
КлассыСсылок.Вставить("ПланыСчетов", "ПланСчетовСсылка");
КлассыСсылок.Вставить("ПланыОбмена", "ПланОбменаСсылка");
КлассыСсылок.Вставить("Задачи", "ЗадачаСсылка");
КлассыСсылок.Вставить("БизнесПроцессы", "БизнесПроцессСсылка");
// Обходим коллекции метаданных циклом
Для Каждого Класс Из КлассыСсылок Цикл
ИмяКоллекции = Класс.Ключ; // Например, "Справочники"
ПрефиксТипа = Класс.Значение; // Например, "СправочникСсылка"
Для Каждого ОбъектМетаданных Из Метаданные[ИмяКоллекции] Цикл
СтрокаТипа = ПрефиксТипа + "." + ОбъектМетаданных.Имя;
СписокТипов.Добавить(СтрокаТипа);
EndDo;
EndDo;
Согласно стандартам разработки фирмы «1С», для оптимальной производительности необходимо соблюдать следующие правила:
Ссылка.Метаданные(). Это работает в разы быстрее, чем поиск через глобальный контекст.Метаданные.НайтиПоТипу(ТипЗнч(Ссылка)).Рассмотрим классическую задачу: у нас есть текстовое представление уникального идентификатора (GUID), и нам необходимо найти, какому именно объекту в базе данных он принадлежит. Тип объекта (справочник, документ или план счетов) нам неизвестен.
Простейший «лобовой» алгоритм заключается в циклическом переборе менеджеров всех возможных ссылочных типов метаданных, генерации ссылки по GUID в памяти и последующей попытке прочитать этот объект из базы данных. Выглядит это решение следующим образом:
&НаСервере
Процедура ПоискОбъектаПоGUIDНаСервере()
УИ = Новый УникальныйИдентификатор(Объект.GUID);
НайденнаяСсылка = Неопределено;
// Последовательно опрашиваем менеджеры основных ссылочных классов
Если ПолучитьСсылкуПоМенеджеруОбъекта(Справочники, УИ, НайденнаяСсылка) Тогда
Объект.НайденнаяСсылка = НайденнаяСсылка;
ИначеЕсли ПолучитьСсылкуПоМенеджеруОбъекта(Документы, УИ, НайденнаяСсылка) Тогда
Объект.НайденнаяСсылка = НайденнаяСсылка;
ИначеЕсли ПолучитьСсылкуПоМенеджеруОбъекта(ПланыВидовХарактеристик, УИ, НайденнаяСсылка) Тогда
Объект.НайденнаяСсылка = НайденнаяСсылка;
ИначеЕсли ПолучитьСсылкуПоМенеджеруОбъекта(ПланыСчетов, УИ, НайденнаяСсылка) Тогда
Объект.НайденнаяСсылка = НайденнаяСсылка;
ИначеЕсли ПолучитьСсылкуПоМенеджеруОбъекта(ПланыОбмена, УИ, НайденнаяСсылка) Тогда
Объект.НайденнаяСсылка = НайденнаяСсылка;
ИначеЕсли ПолучитьСсылкуПоМенеджеруОбъекта(БизнесПроцессы, УИ, НайденнаяСсылка) Тогда
Объект.НайденнаяСсылка = НайденнаяСсылка;
ИначеЕсли ПолучитьСсылкуПоМенеджеруОбъекта(Задачи, УИ, НайденнаяСсылка) Тогда
Объект.НайденнаяСсылка = НайденнаяСсылка;
Иначе
Сообщить("Объект с таким GUID в базе данных не найден.");
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ПолучитьСсылкуПоМенеджеруОбъекта(ОбъектыМенеджер, УИ, НайденнаяСсылка)
Для Каждого Менеджер Из ОбъектыМенеджер Цикл
// Создаем ссылку в оперативной памяти без обращения к СУБД
ПроверяемаяСсылка = Менеджер.ПолучитьСсылку(УИ);
// Пытаемся получить объект. Если он существует - возвращается объект, если нет - Неопределено
Если ПроверяемаяСсылка.ПолучитьОбъект() <> Неопределено Тогда
НайденнаяСсылка = ПроверяемаяСсылка;
Возврат Истина;
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Несмотря на то, что приведенный выше код полностью работоспособен, он обладает критическим недостатком производительности.
Метод Менеджер.ПолучитьСсылку(УИ) выполняется мгновенно, так как он просто генерирует ссылку в памяти на основе переданного GUID. Однако метод Ссылка.ПолучитьОбъект() инициирует полноценное чтение всей записи из СУБД со всеми ее реквизитами, табличными частями и дополнительными сведениями.
Если ваша конфигурация содержит сотни справочников и документов (например, как в ERP, УТ или КА), выполнение этого кода приведет к генерации сотен тяжелых SQL-запросов. Это вызовет существенное зависание серверного вызова и создаст колоссальную, неоправданную нагрузку на сервер баз данных.
Для ускорения работы этой операции мы можем применить два альтернативных профессиональных подхода.
Если ваше решение развернуто на любой современной типовой конфигурации 1С, в ней гарантированно присутствует БСП. В общем модуле ОбщегоНазначения предусмотрена готовая функция СсылкаСуществует().
Вместо тяжелого метода ПолучитьОбъект() мы можем переписать проверку существования записи в базе данных следующим образом:
// Вместо: Если ПроверяемаяСсылка.ПолучитьОбъект() <> Неопределено
Если ОбщегоНазначения.СсылкаСуществует(ПроверяемаяСсылка) Тогда
НайденнаяСсылка = ПроверяемаяСсылка;
Возврат Истина;
КонецЕсли;
Внутри этой функции БСП выполняет быстрый и легкий запрос вида: ВЫБРАТЬ Ссылка ИЗ [Таблица] ГДЕ Ссылка = &Ссылка. Это защищает СУБД от избыточного чтения тяжелых полей и ускоряет поиск в десятки раз.
Максимальную скорость поиска можно получить, если минимизировать количество обращений к СУБД до одного-единственного запроса. Мы можем динамически обойти все метаданные в памяти сервера приложений, сгенерировать единый текст запроса, объединяющий выборки из всех таблиц через оператор ОБЪЕДИНИТЬ ВСЕ, и выполнить его за один раз.
Рассмотрим пример реализации такого алгоритма:
&НаСервере
Функция НайтиСсылкуПоGUIDЧерезЗапрос(УИД) Экспорт
Запрос = Новый Запрос;
ТекстЗапроса = "";
// Задаем массив классов метаданных для поиска
МассивКлассов = Новый Массив;
МассивКлассов.Добавить(Метаданные.Справочники);
МассивКлассов.Добавить(Метаданные.Документы);
МассивКлассов.Добавить(Метаданные.ПланыВидовХарактеристик);
МассивКлассов.Добавить(Метаданные.ПланыСчетов);
МассивКлассов.Добавить(Метаданные.ПланыОбмена);
МассивКлассов.Добавить(Метаданные.Задачи);
МассивКлассов.Добавить(Метаданные.БизнесПроцессы);
Для Каждого Класс Из МассивКлассов Цикл
Для Каждого ОбъектМетаданных Из Класс Цикл
ИмяТаблицы = ОбъектМетаданных.ПолноеИмя();
ИмяПараметра = "Параметр_" + СтрЗаменить(ИмяТаблицы, ".", "_");
Если ТекстЗапроса <> "" Тогда
ТекстЗапроса = ТекстЗапроса + Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС;
КонецЕсли;
// Формируем легкий точечный запрос к таблице
ТекстЗапроса = ТекстЗапроса + "ВЫБРАТЬ Ссылка КАК Ссылка ИЗ " + ИмяТаблицы + " ГДЕ Ссылка = &" + ИмяПараметра;
// Получаем ссылку в памяти через динамическое обращение к менеджеру
ИмяКласса = ОбъектМетаданных.Родитель().Имя; // Например, "Справочники"
ПроверяемаяСсылка = [ИмяКласса][ОбъектМетаданных.Имя].ПолучитьСсылку(УИД);
Запрос.УстановитьПараметр(ИмяПараметра, ПроверяемаяСсылка);
КонецЦикла;
КонецЦикла;
Запрос.Текст = ТекстЗапроса;
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Выборка.Следующий();
Возврат Выборка.Ссылка;
КонецЕсли;
Возврат Неопределено;
КонецФункции
Этот способ отрабатывает мгновенно, поскольку СУБД выполняет поиск по индексу первичного ключа (кластеризованному индексу Ссылка) сразу по всей базе данных за одно транзакционное обращение.
Часто задача поиска объекта по уникальному идентификатору возникает в базах данных, когда в полях документов или отчетов пользователи видят пугающую системную надпись:
«<Объект не найден> (84:bf5600145e3710ab11dda4c605dbe824)»
Если у вас есть строковое представление такой неразрешенной («битой») ссылки, то сканировать всю базу данных и перебирать метаданные вообще не требуется!
Внутренний формат представления ненайденного объекта в платформе 1С уже содержит всю исчерпывающую информацию для точного восстановления типа:
84) представляют собой шестнадцатеричный идентификатор типа объекта метаданных в структуре СУБД конкретной информационной базы.bf5600145e3710ab11dda4c605dbe824) — это шестнадцатеричное представление самого уникального идентификатора (GUID) в инвертированном байтовом порядке.Платформа 1С умеет автоматически разбирать такие строки и инициализировать битую ссылку с правильным типом метаданных с помощью механизмов XML-сериализации. Для этого достаточно восстановить XML-структуру ссылки и передать ее XML-десериализатору платформы. Платформа сама мгновенно определит нужную таблицу (например, поймет, что это справочник контрагентов) и вернет пустую ссылку нужного типа. Вам останется лишь считать представление этой ссылки или обработать ее в соответствии с требованиями вашей бизнес-логики.