Как проверить существование объекта в приемнике при конвертации данных и правильно обработать набор записей регистра сведений?

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

При настройке правил обмена в «1С:Конвертации данных 2.1» (КД 2.1) разработчики часто сталкиваются с необходимостью отсечь загрузку битых ссылок. Типичный сценарий: выгружается регистр сведений (например, Штрихкоды), где в качестве измерения или реквизита выступает ссылка на объект (например, Владелец — справочник Номенклатура). Если в базе-приемнике этого объекта нет, а правила настроены так, что сама номенклатура не переносится, в приемнике создается «битая ссылка» (объект не найден).

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

---

Разберем причину ошибки «Поле объекта не обнаружено»

Часто начинающие разработчики пытаются обратиться к свойствам объекта в обработчике «После загрузки» ПКО регистра сведений напрямую, например, так:


Если СокрЛП(Объект.Владелец.Код) = "" Тогда
    Отказ = Истина;
КонецЕсли;

При выполнении этого кода платформа выдает ошибку: «Поле объекта не обнаружено (Владелец)». Проанализируем ситуацию и выясним причину.

В отличие от справочников и документов, для которых переменная Объект представляет собой конкретную ссылку или объект базы данных (СправочникОбъект, ДокументОбъект), для регистров сведений переменная Объект является набором записей (РегистрСведенийНаборЗаписей).

Сам по себе набор записей — это коллекция строк. У набора записей нет и не может быть реквизитов конкретной записи (таких как Владелец, Штрихкод и т.д.). Эти свойства есть только у конкретных строк (записей), входящих в этот набор. Именно поэтому обращение Объект.Владелец вызывает закономерную ошибку платформы.

Чтобы обратиться к данным, нам необходимо работать с элементами коллекции набора. Например, получить первую запись по индексу:


ПерваяЗапись = Объект[0];
Владелец = ПерваяЗапись.Владелец;

---

Как правильно проверить существование объекта по ссылке (проверка на «битую» ссылку)

Попытка проверить существование ссылки через конструкцию СокрЛП(Объект[0].Владелец.Код) = "" является грубой ошибкой проектирования. При обращении через точку к реквизитам несуществующей ссылки платформа делает неявный SQL-запрос к базе данных. Это существенно снижает скорость загрузки, а если тип данных составной или у объекта отсутствует реквизит Код, система аварийно завершит работу.

Рассмотрим правильные и быстрые способы проверки существования ссылки в базе-приемнике.

Способ 1. Быстрый запрос к базе данных (Рекомендуемый)

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


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

Способ 2. Использование функций БСП

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


Если НЕ ОбщегоНазначения.СсылкаСуществует(Запись.Владелец) Тогда
    // Ссылка является битой
КонецЕсли;

Способ 3. Метод ПолучитьОбъект() (для простых конфигураций)

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


Если Запись.Владелец.ПолучитьОбъект() = Неопределено Тогда
    // Ссылка битая
КонецЕсли;

Важно: Не используйте ПолучитьОбъект() при обработке больших массивов данных в циклах, так как этот метод полностью считывает весь объект в память, что крайне непроизводительно.

---

Почему Отказ = Истина не работает для выборочных записей регистра сведений?

В процессе обсуждения на форуме автор столкнулся с тем, что установка флага Отказ = Истина в обработчике «После загрузки» ПКО регистра сведений не решает задачу выборочной фильтрации записей.

Дело в том, что Отказ = Истина отменяет запись всего набора полностью. Вы не можете с его помощью сказать системе: «эту строчку запиши, а вот эту выброси». Более того, в некоторых версиях обработок обмена данными флаг Отказ в обработчике «После загрузки» для наборов записей регистров сведений игнорировался платформой из-за особенностей механизмов записи.

Правильная стратегия: не отменять загрузку всего объекта через Отказ, а программно модифицировать коллекцию Объект (набор записей), удаляя из нее некорректные строки непосредственно перед записью в базу данных.

---

Как правильно удалять записи из набора в цикле

Попытка удалять элементы коллекции прямо во время ее обхода стандартным циклом Для Каждого — классическая ошибка разработчиков. Посмотрим на примере, почему так делать нельзя:


// ВНИМАНИЕ: Так делать НЕЛЬЗЯ!
Для Каждого Запись Из Объект Цикл
    Если НЕ СсылкаСуществует(Запись.Владелец) Тогда
        Объект.Удалить(Запись); // Ошибка! Сбиваются индексы обхода
    КонецЕсли;
КонецЦикла;

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

Рассмотрим три безопасных способа обхода коллекции с удалением элементов.

Вариант А. Обход коллекции с конца (Обратный цикл)

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


КоличествоЗаписей = Объект.Количество();

Для Индекс = -(КоличествоЗаписей - 1) По 0 Цикл
    ТекущийИндекс = -Индекс;
    Запись = Объект[ТекущийИндекс];
    
    Если НЕ СсылкаСуществует(Запись.Владелец) Тогда
        Объект.Удалить(ТекущийИндекс);
    КонецЕсли;
КонецЦикла;

Или более простой для понимания вариант синтаксиса обратного цикла:


Для Шаг = 1 По Объект.Количество() Цикл
    Индекс = Объект.Количество() - Шаг;
    Запись = Объект[Индекс];
    
    Если НЕ СсылкаСуществует(Запись.Владелец) Тогда
        Объект.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

Вариант Б. Использование вспомогательного массива

Сначала мы в стандартном цикле находим все «плохие» записи и складываем их во временный массив. После завершения основного цикла мы просто обходим наш массив и удаляем накопленные записи из набора.


МассивНаУдаление = Новый Массив;

// Шаг 1. Собираем элементы для удаления
Для Каждого Запись Из Объект Цикл
    Если НЕ СсылкаСуществует(Запись.Владелец) Тогда
        МассивНаУдаление.Добавить(Запись);
    КонецЕсли;
КонецЦикла;

// Шаг 2. Удаляем собранные элементы из исходного набора
Для Каждого ЭлементНаУдаление Из МассивНаУдаление Цикл
    Объект.Удалить(ЭлементНаУдаление);
КонецЦикла;

Вариант В. Выгрузка в Таблицу Значений

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


// Выгружаем набор в Таблицу Значений
ТаблицаДанных = Объект.Выгрузить();

// Проводим фильтрацию строк в ТЗ с конца
Для Индекс = -(ТаблицаДанных.Количество() - 1) По 0 Цикл
    СтрокаТЗ = ТаблицаДанных[-Индекс];
    Если НЕ СсылкаСуществует(СтрокаТЗ.Владелец) Тогда
        ТаблицаДанных.Удалить(СтрокаТЗ);
    КонецЕсли;
КонецЦикла;

// Очищаем набор и загружаем отфильтрованные данные обратно
Объект.Очистить();
Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
    ЗаполнитьЗначенияСвойств(Объект.Добавить(), СтрокаТЗ);
КонецЦикла;

---

Итоговое решение для обработчика «После загрузки» ПКО

Объединим все наши выводы в готовый и безопасный шаблон кода, который вы можете вставить в обработчик события «После загрузки» для правил конвертации объектов (ПКО) вашего регистра сведений.


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

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

// Удаляем плохие записи из набора
Для Каждого ЗаписьДляУдаления Из МассивНаУдаление Цикл
    Объект.Удалить(ЗаписьДляУдаления);
КонецЦикла;

// Если после очистки в наборе не осталось ни одной строки,
// мы можем вообще отказаться от записи этого набора в базу данных
Если Объект.Количество() = 0 Тогда
    Отказ = Истина;
КонецЕсли;

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

← На главную