Как правильно заполнить реквизит типа ссылка в 1С и избежать ошибки «Значение не является значением объектного типа»

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

Каждый начинающий разработчик в среде «1С:Предприятие 8» рано или поздно сталкивается с задачами массового изменения или заполнения реквизитов в справочниках. Казалось бы, задача тривиальная: обойти элементы, проверить, заполнено ли поле, и, если нет, записать нужное значение. Однако на практике здесь кроется множество подводных камней, связанных со спецификой типов данных в 1С, работой с пустой ссылкой, значениями NULL, Неопределено и производительностью базы данных.

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

Анализ исходной проблемы и разбор ошибок

Давайте проанализируем код, с которого началась дискуссия на форуме. Разработчик пытался заполнить реквизит БазоваяЕдиницаИзмерения в справочнике Номенклатура конкретным значением (кодом 796, что соответствует штукам в классификаторе ОКЕИ):


Номенклатура = Справочники.Номенклатура; 
Выборка = Номенклатура.Выбрать(); 
Пока Выборка.Следующий() Цикл 
    Объект = Выборка.ПолучитьОбъект(); 
    Если Объект.БазоваяЕдиницаИзмерения = NULL Тогда 
        Если Объект.ЭтоГруппа Тогда 
            Продолжить; 
        Иначе 
            Объект.БазоваяЕдиницаИзмерения = 796; 
            Объект.Записать(); 
        КонецЕсли; 
    КонецЕсли; 
КонецЦикла;

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

  1. Прямое сравнение с NULL: Встроенный язык 1С не поддерживает такое сравнение в обычном коде (вне запросов). Конструкция Объект.БазоваяЕдиницаИзмерения = NULL всегда будет возвращать Ложь, и код внутри условия просто никогда не выполнится.
  2. Присвоение числа ссылочному реквизиту: Строка Объект.БазоваяЕдиницаИзмерения = 796 ошибочна. Реквизит БазоваяЕдиницаИзмерения имеет тип ссылки (например, СправочникСсылка.КлассификаторЕдиницИзмерения). Платформа не умеет автоматически преобразовывать число 796 в нужную ссылку. Ей необходимо передать именно объект ссылки.
  3. Неэффективный обход справочника: Использование метода Выбрать() и последующее получение объекта через ПолучитьОбъект() для каждой записи — это тяжелая операция, которая заставляет СУБД считывать терабайты лишней информации и блокировать таблицы.

Выясняем причину ошибки «Значение не является значением объектного типа (Пустая)»

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

В платформе 1С отсутствие значения в реквизите может выражаться тремя разными способами:

Универсальное решение проверки: функция ЗначениеЗаполнено()

Для того чтобы обезопасить свой код от непредвиденных типов данных и возможных ошибок, в платформе 1С существует универсальная и очень быстрая функция — ЗначениеЗаполнено().

Давайте посмотрим, как она работает. Функция принимает на вход проверяемое значение и возвращает Истина, если значение заполнено, и Ложь в следующих случаях:

Таким образом, проверка на незаполненный реквизит должна выглядеть следующим образом:


Если НЕ ЗначениеЗаполнено(Объект.БазоваяЕдиницаИзмерения) Тогда
    // Поле пустое, здесь пишем код заполнения
КонецЕсли;

Это самый безопасный, надежный и рекомендуемый стандарт разработки на платформе «1С:Предприятие 8».

Правильное заполнение ссылочного реквизита

Теперь разберем вторую проблему: как присвоить ссылочному полю нужное значение. Как мы уже выяснили, просто написать число или строку нельзя. Платформе нужна ссылка на элемент справочника КлассификаторЕдиницИзмерения.

Чтобы получить эту ссылку, нам нужно найти элемент в базе данных. Чаще всего поиск осуществляют по коду или по наименованию. Обратите внимание, что коды в справочниках 1С почти всегда имеют текстовый тип (иногда с ведущими нулями). Поэтому передавать в поиск нужно именно строку:


// Находим ссылку на элемент классификатора по его коду (796 - штуки)
СсылкаНаЕдиницу = Справочники.КлассификаторЕдиницИзмерения.FindByCode("796"); // Англоязычный аналог НайтиПоКоду

// Проверяем, нашли ли мы элемент в базе
Если ЗначениеЗаполнено(СсылкаНаЕдиницу) Тогда
    Объект.БазоваяЕдиницаИзмерения = СсылкаНаЕдиницу;
Иначе
    Сообщить("Не удалось найти единицу измерения с кодом 796 в классификаторе!");
КонецЕсли;

Оптимизация производительности: почему запросы лучше выборки

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

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

Проанализируем оптимальный код решения этой задачи:


// 1. Создаем запрос для выборки только нужных элементов
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Номенклатура.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |	НЕ Номенклатура.ЭтоГруппа
    |	И Номенклатура.БазоваяЕдиницаИзмерения = ЗНАЧЕНИЕ(Справочник.КлассификаторЕдиницИзмерения.ПустаяСсылка)";

РезультатЗапроса = Запрос.Выполнить();

// Если запрос вернул пустой результат, то и делать ничего не нужно
Если НЕ РезультатЗапроса.Пустой() Тогда
    
    // Находим элемент для заполнения один раз перед циклом!
    ЕдиницаШтуки = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("796");
    
    Если ЗначениеЗаполнено(ЕдиницаШтуки) Тогда
        
        Выборка = РезультатЗапроса.Выбрать();
        Пока Выборка.Следующий() Цикл
            
            // Получаем объект только для тех элементов, которые точно требуют изменения
            Попытка
                ОбъектНоменклатуры = Выборка.Ссылка.ПолучитьОбъект();
                ОбъектНоменклатуры.БазоваяЕдиницаИзмерения = ЕдиницаШтуки;
                ОбъектНоменклатуры.Записать();
            Исключение
                // Обязательно обрабатываем возможные блокировки или ошибки прав доступа
                ЗаписьЖурналаРегистрации("ОбновлениеНоменклатуры",
                    УровеньЖурналаРегистрации.Ошибка,
                    ОбъектНоменклатуры.Метаданные(),
                    Выборка.Ссылка,
                    ОписаниеОшибки());
            КонецПопытки;
            
        КонецЦикла;
        
    Иначе
        ВызватьИсключение "В классификаторе не найдена единица измерения с кодом 796!";
    КонецЕсли;
    
КонецЕсли;

Этот код отработает в десятки раз быстрее исходного варианта, поскольку база данных вернет только целевые записи, а серверу 1С не придется тратить ресурсы на обработку заполненных элементов и папок группы.

Решение дополнительной задачи: Изменение реквизитов в конкретной папке

В ходе обсуждения в сообщении №7 возник дополнительный вопрос: как ограничить обработку и заполнять единицы измерения только для товаров, находящихся в определенной папке (и ее подпапках) справочника Номенклатура?

Для решения этой задачи в запросах 1С используется мощный оператор В ИЕРАРХИИ. Он позволяет автоматически выбрать все вложенные элементы на любом уровне вложенности относительно указанной группы-родителя.

Рассмотрим пример реализации с отбором по конкретной папке:


// 1. Находим нужную целевую папку (например, по коду или по наименованию)
ЦелеваяПапка = Справочники.Номенклатура.НайтиПоКоду("УТ-000015");

Если НЕ ЗначениеЗаполнено(ЦелеваяПапка) Тогда
    ВызватьИсключение "Указанная группа номенклатуры не найдена в базе данных!";
КонецЕсли;

// 2. Формируем запрос с условием "В ИЕРАРХИИ"
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Номенклатура.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |	Номенклатура.Ссылка В ИЕРАРХИИ(&ЦелеваяПапка)
    |	И НЕ Номенклатура.ЭтоГруппа
    |	И Номенклатура.БазоваяЕдиницаИзмерения = ЗНАЧЕНИЕ(Справочник.КлассификаторЕдиницИзмерения.ПустаяСсылка)";

Запрос.УстановитьПараметр("ЦелеваяПапка", ЦелеваяПапка);

РезультатЗапроса = Запрос.Выполнить();

// 3. Обходим только отобранные элементы внутри иерархии папки
Если НЕ РезультатЗапроса.Пустой() Тогда
    
    ЕдиницаШтуки = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("796");
    
    Если ЗначениеЗаполнено(ЕдиницаШтуки) Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Пока Выборка.Следующий() Цикл
            Попытка
                ОбъектНоменклатуры = Выборка.Ссылка.ПолучитьОбъект();
                ОбъектНоменклатуры.БазоваяЕдиницаИзмерения = ЕдиницаШтуки;
                ОбъектНоменклатуры.Записать();
            Исключение
                Сообщить("Не удалось записать элемент: " + Выборка.Ссылка + ". Ошибка: " + ОписаниеОшибки());
            КонецПопытки;
        КонецЦикла;
    КонецЕсли;
    
КонецЕсли;

Использование этого метода гарантирует, что изменения затронут исключительно товары внутри выбранной папки (включая все подпапки любой глубины), а структура групп останется нетронутой благодаря фильтру НЕ Номенклатура.ЭтоГруппа.

← На главную