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