Как красиво и правильно проверить ссылку на «Объект не найден» в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки в среде «1С:Предприятие 8» программисты часто сталкиваются с проблемой появления в базе данных так называемых «битых» ссылок. Это происходит, когда объект (например, документ или элемент справочника) физически удаляется из базы данных, но в других объектах или регистрах сохраняются ссылки на него. В интерфейсе пользователя такие ссылки отображаются в виде некрасивой строки <Объект не найден> (уникальный идентификатор).

В этой статье мы подробно разберем все существующие способы проверки ссылок на существование, сравним их эффективность, проанализируем влияние прав доступа и выберем наиболее оптимальные подходы для различных сценариев разработки.

Метод 1. Проверка существования через запрос (Рекомендуемый способ)

Этот метод считается классическим, наиболее надежным и правильным с точки зрения архитектуры СУБД. Мы обращаемся к таблице базы данных и проверяем, есть ли в ней запись с нашей ссылкой. Если запрос возвращает пустой результат, значит, физического объекта в базе нет.

Рассмотрим пример функции, которая динамически определяет имя таблицы метаданных и выполняет точечный запрос к базе данных:


Функция СсылкаСуществует(ЛюбаяСсылка) Экспорт
    
    // Проверяем, что ссылка заполнена и имеет корректный тип
    Если НЕ ЗначениеЗаполнено(ЛюбаяСсылка) Тогда
        Возврат Ложь;
    КонецЕсли;
    
    ТекстЗапроса = "
    |ВЫБРАТЬ
    |    Ссылка КАК Ссылка
    |ИЗ
    |    [ИмяТаблицы]
    |ГДЕ
    |    Ссылка = &Ссылка
    |";
    
    // Подставляем полное имя таблицы (например, Справочник.Номенклатура)
    ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "[ИмяТаблицы]", ЛюбаяСсылка.Метаданные().ПолноеИмя());
    
    Запрос = Новый Запрос;
    Запрос.Текст = ТекстЗапроса;
    Запрос.УстановитьПараметр("Ссылка", ЛюбаяСсылка);
    
    Возврат НЕ Запрос.Выполнить().Пустой();
     
КонецФункции

Преимущества: этот метод работает чрезвычайно быстро на уровне СУБД, так как поиск происходит по первичному ключу (кластерному индексу), и база данных не считывает лишних полей.

Важный нюанс: Ограничения прав доступа (RLS)

Проанализируем ситуацию, когда у пользователя настроены ограничения прав на уровне записей (RLS). Если выполнять указанный выше запрос под сеансом пользователя с ограниченными правами, то для объектов, на чтение которых у него нет прав, запрос вернет пустой результат. В итоге программа ошибочно посчитает существующую ссылку «битой».

Решение: Для проверки физического существования записи в БД код обязательно должен выполняться в привилегированном режиме. Оберните вызов функции в привилегированный общий модуль или принудительно включите привилегированный режим на время выполнения проверки:


УстановитьПривилегированныйРежим(Истина);
Существует = СсылкаСуществует(ПроверяемаяСсылка);
УстановитьПривилегированныйРежим(Ложь);

Метод 2. Стандартные методы БСП

Если вы ведете разработку в конфигурации, созданной на основе Библиотеки стандартных подсистем (БСП), то изобретать велосипед не нужно. В составе БСП уже есть готовые, оптимизированные функции в общем модуле ОбщегоНазначения.

Рассмотрим два основных инструмента:

  1. ОбщегоНазначения.СсылкаСуществует(ПроверяемаяСсылка) — выполняет быстрый запрос в привилегированном режиме и возвращает булево значение. Это готовый аналог метода, описанного выше.
  2. ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита) — если вам требуется не просто проверить факт существования объекта, но и получить его реквизит (например, проверить, помечен ли он на удаление), используйте эту функцию. Если ссылка окажется битой, функция не вызовет критической ошибки платформы, а вернет значение Неопределено. При этом платформа не будет считывать в кэш весь объект целиком, что экономит оперативную память сервера.

Метод 3. Получение объекта в памяти (ПолучитьОбъект)

Простой способ в одну строку, который часто используют начинающие разработчики:

ЕстьОбъект = (Ссылка.ПолучитьОбъект() <> Неопределено);

Почему этот способ нежелателен в высоконагруженных системах? Метод ПолучитьОбъект() считывает из СУБД абсолютно все реквизиты объекта, включая все его табличные части, и помещает их в оперативную память. Если вы проверяете тяжелый многострочный документ или большой справочник, это создает колоссальную избыточную нагрузку на сервер и дисковую подсистему.

Метод 4. Проверка через свойство ВерсияДанных

Вариант, позволяющий избежать явного написания запросов:

ЕстьОбъект = ЗначениеЗаполнено(Ссылка.ВерсияДанных);

Особенности работы: При обращении к свойству ВерсияДанных «через точку» платформа выполняет неявное считывание объекта. По производительности этот метод практически идентичен методу ПолучитьОбъект() и уступает специализированному точечному запросу.

Метод 5. Преобразование ссылки в строку (Быстро, но опасно!)

В процессе обсуждения часто всплывает «лайфхак» с поиском подстроки в строковом представлении объекта:

ЕстьОбъект = (Найти(Строка(Ссылка), "<Объект не") = 0);

Несмотря на то, что замеры на файловой базе могут показывать высокую скорость выполнения этой операции, применять такой метод в промышленной разработке категорически запрещено по следующим причинам:

Опасное заблуждение: Проверка через конструкцию Попытка-Исключение

Иногда на просторах интернета можно встретить рекомендацию использовать конструкцию:


Попытка
    Тест = Ссылка.Ссылка;
Исключение
    // Объект не найден
КонецПопытки

Важно знать: Этот способ не работает. Обращение к свойству Ссылка у битой ссылки просто возвращает эту же ссылку и никогда не вызывает исключения платформы. Проверка всегда будет проходить успешно, даже если объекта физически не существует в базе данных.

Пакетная проверка множества ссылок

Если перед вами стоит задача проверить массив из сотен или тысяч ссылок, выполнять точечные запросы в цикле — грубейшая ошибка, которая приведет к зависанию системы. В таких случаях необходимо использовать пакетный запрос.

Рассмотрим алгоритм групповой проверки:


// Предположим, у нас есть МассивСсылокРазныхТипов

// Шаг 1. Группируем ссылки по типам метаданных для оптимизации запросов
ТаблицаПоТипам = Новый Соответствие;

Для Каждого СсылкаИзМассива Из МассивСсылокРазныхТипов Цикл
    ТипОбъекта = СсылкаИзМассива.Метаданные();
    МассивДляТипа = ТаблицаПоТипам.Получить(ТипОбъекта);
    Если МассивДляТипа = Неопределено Тогда
        МассивДляТипа = Новый Массив;
        ТаблицаПоТипам.Вставить(ТипОбъекта, МассивДляТипа);
    КонецЕсли;
    МассивДляТипа.Добавить(СсылкаИзМассива);
КонецЦикла;

// Шаг 2. Выполняем запросы по каждому типу метаданных
СуществующиеСсылки = Новый Массив;

Для Каждого КлючИЗначение Из ТаблицаПоТипам Цикл
    МетаданныеТипа = КлючИЗначение.Ключ;
    МассивКонкретныхСсылок = КлючИЗначение.Значение;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    Таб.Ссылка КАК Ссылка
    |ИЗ
    |    " + МетаданныеТипа.ПолноеИмя() + " КАК Таб
    |ГДЕ
    |    Таб.Ссылка В (&МассивСсылок)";
    
    Запрос.УстановитьПараметр("МассивСсылок", МассивКонкретныхСсылок);
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        СуществующиеСсылки.Добавить(Выборка.Ссылка);
    КонецЦикла;
КонецЦикла;

// В результате в массиве "СуществующиеСсылки" останутся только «живые» объекты

Проверка «битых» ссылок внутри запросов

Если вы пишете сложный отчет или обработку и хотите прямо в основном запросе отсечь несуществующие элементы (например, битые ссылки в измерениях регистра), используйте левое соединение или обращение к реквизиту через точку в комбинации с проверкой на NULL.

Посмотрим на пример условия в запросе:


ГДЕ 
    НЕ ТаблицаПродаж.Номенклатура = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
    И ТаблицаПродаж.Номенклатура.Ссылка ЕСТЬ NULL

Как это работает: Если номенклатура заполнена (не пустая ссылка), но при этом обращение к реквизиту Номенклатура.Ссылка возвращает значение NULL, это однозначно свидетельствует о том, что объект был физически удален из базы данных, то есть ссылка является «битой».

← На главную