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