В процессе разработки на платформе 1С:Предприятие 8 мы часто сталкиваемся с ситуацией, когда в реквизите хранится ссылка, но при попытке обращения к ней мы видим надпись «Объект не найден». Это происходит, когда физическая запись в таблице базы данных была удалена, но ссылка на неё (UUID) осталась в других документах или регистрах. В этой статье мы подробно разберем, как определить «битость» ссылки программно, не используя медленную конструкцию Попытка ... Исключение, и выберем наиболее производительный метод.
Прежде чем мы перейдем к коду, давайте проанализируем, что такое ссылка. Ссылка в 1С — это совокупность типа данных и уникального идентификатора (GUID). Сама по себе переменная типа ЛюбаяСсылка может содержать идентификатор, но это не гарантирует наличие соответствующей записи в СУБД. Наша задача — выяснить, есть ли в таблице объекта запись с таким идентификатором.
Рассмотрим самый быстрый и надежный способ. Использование запроса позволяет нам обратиться напрямую к индексу таблицы базы данных. Мы не загружаем в память весь объект с его табличными частями и движениями, что критически важно для производительности.
Для универсальной проверки нам сначала нужно определить имя таблицы. Разберем по шагам создание функции СсылкаСуществует:
&НаСервере
Функция СсылкаСуществует(ЛюбаяСсылка) Экспорт
// Если ссылка пустая, то объекта заведомо нет в базе
Если ЛюбаяСсылка.Пустая() Тогда
Возврат Ложь;
КонецЕсли;
// Получаем полное имя метаданных.
// Можно использовать Метаданные.НайтиПоТипу(ТипЗнч(ЛюбаяСсылка)).ПолноеИмя,
// но использование СериализаторXDTO или XMLТипЗнч иногда работает быстрее.
ТипСсылки = XMLТипЗнч(ЛюбаяСсылка);
ИмяТаблицы = СтрЗаменить(ТипСсылки.ИмяТипа, "Ref.", ".");
ТекстЗапроса = "ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Ссылка
|ИЗ
| [ИмяТаблицы]
|ГДЕ
| Ссылка = &Ссылка";
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "[ИмяТаблицы]", ИмяТаблицы);
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Ссылка", ЛюбаяСсылка);
// Важно использовать привилегированный режим, чтобы RLS не скрыл существующий объект
УстановитьПривилегированныйРежим(Истина);
Результат = Запрос.Выполнить();
УстановитьПривилегированныйРежим(Ложь);
Возврат НЕ Результат.Пустой();
КонецФункции
Почему это лучше: Обратите внимание на использование ключевого слова РАЗРЕШЕННЫЕ и привилегированного режима. Если у пользователя настроены ограничения RLS, обычный запрос может не найти объект, даже если он есть. Если ваша цель — проверить именно физическое наличие, привилегированный режим обязателен.
Рассмотрим метод, который часто используют начинающие разработчики. Метод ПолучитьОбъект() возвращает Неопределено, если ссылки в базе нет. Однако проанализируем последствия: при вызове этого метода платформа считывает из базы все реквизиты, все строки всех табличных частей и подготавливает объект к работе. Если это «тяжелый» документ с тысячами строк, ваша проверка будет длиться секунды.
Если Ссылка.ПолучитьОбъект() = Неопределено Тогда
Сообщить("Ссылка битая!");
КонецЕсли;
Важное предупреждение: Кроме низкой скорости, вызов ПолучитьОбъект() запускает код в модуле объекта (процедура ПриЧтенииНаСервере или инициализация переменных модуля). Это может привести к нежелательным побочным эффектам или дополнительным задержкам.
Некоторые программисты пытаются проверить наличие объекта через обращение к реквизиту, например, Ссылка.ВерсияДанных или Ссылка.Наименование. Выясним причину, почему это плохая идея: как только вы обращаетесь к реквизиту через точку, 1С автоматически выполняет ПолучитьОбъект() в кэш. Выигрыша в скорости по сравнению со вторым способом не будет.
// Это будет работать так же медленно, как ПолучитьОбъект()
Если ЗначениеЗаполнено(Ссылка.ВерсияДанных) Тогда
// Объект существует
КонецЕсли;
Если вам нужно проверить не одну ссылку, а целый список (например, в табличной части документа), эффективнее всего использовать левое соединение в запросе или проверку через обращение к полям ссылки. Посмотрим на пример поиска битых ссылок в справочнике номенклатуры:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Номенклатура КАК БитаяСсылка
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Док
|ГДЕ
| Док.Ссылка = &Документ
| И Док.Номенклатура.Ссылка ЕСТЬ NULL";
// Обращение через точку к .Ссылка в условии ЕСТЬ NULL — классический прием 1С
В этом примере Док.Номенклатура.Ссылка вернет NULL для всех битых ссылок, что позволит мгновенно отфильтровать их в одном запросе.
Существует альтернативный способ через механизм навигационных ссылок. Он интересен тем, что позволяет работать с представлениями без прямого обращения к таблицам СУБД через Запрос. Разберем пример:
Функция ЭтоБитаяСсылкаНавигация(Ссылка)
МассивСсылок = Новый Массив;
МассивСсылок.Добавить(ПолучитьНавигационнуюСсылку(Ссылка));
Представления = ПолучитьПредставленияНавигационныхСсылок(МассивСсылок);
// Для битых ссылок Текст представления будет содержать GUID или специфическую строку
Возврат Представления[0].Текст = "" ИЛИ СтрНайти(Представления[0].Текст, "Объект не найден") > 0;
КонецФункции
Этот метод может быть полезен в специфических сценариях на клиенте, но для серверной логики запрос остается предпочтительным, особенно если выполняется сравнение строки с представлением ссылки в запросе.
Проанализируем результаты замеров (на 1000 итераций):
Для качественного кода в 1С придерживайтесь следующих правил:
ПолучитьОбъект() или обращение через точку только для проверки существования.ЕСТЬ NULL.Ссылка.Пустая() (GUID из нулей) и битой ссылкой (GUID есть, записи нет). Метод ЗначениеЗаполнено() для битой ссылки вернет Истина!Использование этих подходов позволит вашим системам работать быстро и избегать трудноуловимых ошибок, связанных с «невидимостью» данных из-за RLS или лишним чтением тяжелых объектов.