Как исправить ошибку «Значение не является значением объектного типа» при подстановке данных из регистра сведений в табличную часть?

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

При разработке логики заполнения документов часто возникает типовая задача: при изменении значения в строке табличной части необходимо автоматически подтянуть связанные данные из регистра сведений. Например, пользователь указывает инвентарный номер, а система должна автоматически заполнить реквизит Номенклатура.

Однако начинающие разработчики нередко сталкиваются с ошибкой времени выполнения:

{Форма(6)}: Значение не является значением объектного типа (Номенклатура)

Разберем детально, почему возникает это исключение, как устроена работа с методами считывания данных из регистров сведений и как написать надежный, оптимизированный клиент-серверный код.

Анализ исходного кода и причин ошибки

Посмотрим на исходный пример, в котором происходит сбой:


Функция ПодстановкаНоменклатуры(ЭлементИнвентарныйНомер) Экспорт
    Отбор = Новый Структура("ИнвентарныйНомер", ЭлементИнвентарныйНомер);
    ЗначенияРесурсов = РегистрыСведений.ИнвентарныеНомераНоменклатура.ПолучитьПоследнее(Отбор);
    Возврат ЗначенияРесурсов;
КонецФункции

&НаКлиенте
Процедура ПеремещенияИнвентарныйНомерПриИзменении(Элемент)
    СтрокаТабличнойЧасти = Элементы.Перемещения.ТекущиеДанные;
    ЗначенияРесурсов = ПодстановкаНоменклатуры(СтрокаТабличнойЧасти.ИнвентарныйНомер);
    СтрокаТабличнойЧасти.Номенклатура = ЗначенияРесурсов.Номенклатура;
КонецПроцедуры

Ошибка «Значение не является значением объектного типа» означает, что программа пытается обратиться через точку к свойству переменной, которая не является объектом или структурой (чаще всего содержит значение Неопределено или Null). В приведенном коде это может происходить как слева (в переменной СтрокаТабличнойЧасти), так и справа (в переменной ЗначенияРесурсов). Выясним основные причины такого поведения.

  1. Несоответствие периодичности регистра и используемого метода: Если регистр сведений является непериодическим, у его менеджера в принципе отсутствует метод ПолучитьПоследнее(). Для непериодических регистров сведений предназначен исключительно метод Получить().
  2. Ошибочный синтаксис параметров метода: Если регистр все-таки периодический, метод ПолучитьПоследнее() имеет сигнатуру: ПолучитьПоследнее(<КонецПериода>, <Отбор>). Передав структуру отбора первым параметром, разработчик передает ее в качестве даты окончания периода, а параметр отбора остается пустым. В результате метод работает некорректно.
  3. Отсутствие данных в свойстве ТекущиеДанные: На стороне клиента свойство Элементы.ИмяТаблицы.ТекущиеДанные возвращает Неопределено, если табличная часть не содержит строк, потеряла фокус или строка была удалена. Обращение СтрокаТабличнойЧасти.Номенклатура слева от знака равенства вызовет точно такую же ошибку.
  4. Ограничения структуры отбора: В структуру отбора для методов Получить() и ПолучитьПоследнее() разрешено передавать только имена измерений регистра. Если ИнвентарныйНомер создан как ресурс или реквизит, метод завершится аварийно.

Правила использования методов считывания регистра сведений

Проанализируем разницу в поведении платформы в зависимости от структуры метаданных регистра:

  1. Для непериодического регистра сведений: Используем метод Получить(<Отбор>). На вход передается структура, где ключи соответствуют измерениям. Метод возвращает структуру, содержащую значения всех ресурсов регистра на момент вызова.
  2. Для периодического регистра сведений: Используем метод ПолучитьПоследнее(<КонецПериода>, <Отбор>). Первым параметром передается дата/время (или пустая дата для получения самого актуального значения на текущий момент), а вторым параметром — структура отбора.
  3. Поведение при отсутствии записи: Если запись по переданному отбору не найдена, метод Получить() возвращает структуру с именами ресурсов, значения которых равны значениям по умолчанию для соответствующих типов данных (для ссылочных типов — пустая ссылка). Поэтому результат важно проверять функцией ЗначениеЗаполнено(), чтобы случайно не перезаписать уже введенные данные пустой ссылкой.

Оптимизация клиент-серверного взаимодействия

Менеджер регистра сведений РегистрыСведений.<Имя> доступен только на сервере. При организации вызова следует придерживаться стандартов разработки «1С:Предприятие 8»:

  1. Директива компиляции: Чтение данных из базы без изменения данных формы должно выполняться в процедуре или функции с директивой &НаСервереБезКонтекста. Это исключает передачу всего контекста формы по сети и существенно снижает нагрузку на сервер.
  2. Передача параметров: Нельзя передавать объект строки таблицы формы (ДанныеФормыЭлементКоллекции) в неконтекстные методы. На сервер передается только само ключевое значение (в нашем случае инвентарный номер), а сервер возвращает клиенту найденную ссылку.

Шаблон безопасного и правильного кода

Объединим все правила и рассмотрим правильную реализацию обработчика на управляемой форме:


&НаКлиенте
Процедура ПеремещенияИнвентарныйНомерПриИзменении(Элемент)
    СтрокаТабличнойЧасти = Элементы.Перемещения.ТекущиеДанные;
    
    // Защита от обращения к пустому объекту на клиенте
    Если СтрокаТабличнойЧасти = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    НайденнаяНоменклатура = ПолучитьНоменклатуруПоИнвНомеру(СтрокаТабличнойЧасти.ИнвентарныйНомер);
    
    // Подставляем номенклатуру только в том случае, если она найдена в регистре
    Если ЗначениеЗаполнено(НайденнаяНоменклатура) Тогда
        СтрокаТабличнойЧасти.Номенклатура = НайденнаяНоменклатура;
    КонецЕсли;
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьНоменклатуруПоИнвНомеру(ИнвентарныйНомер)
    Если Не ЗначениеЗаполнено(ИнвентарныйНомер) Тогда
        Возврат Справочники.Номенклатура.ПустаяСсылка();
    КонецЕсли;
    
    Отбор = Новый Структура("ИнвентарныйНомер", ИнвентарныйНомер);
    
    // Для непериодического регистра сведений вызываем Получить()
    ЗначенияРесурсов = РегистрыСведений.ИнвентарныеНомераНоменклатура.Получить(Отбор);
    
    // Если регистр периодический, правильный синтаксис был бы:
    // ЗначенияРесурсов = РегистрыСведений.ИнвентарныеНомераНоменклатура.ПолучитьПоследнее(ТекущаяДатаСеанса(), Отбор);
    
    Возврат ЗначенияРесурсов.Номенклатура;
КонецФункции

Альтернативный вариант: получение данных через запрос

Если поле отбора не является измерением регистра сведений (например, объявлено реквизитом) либо структура регистра в будущем может измениться, виртуальные методы менеджера регистра использовать нельзя. В таком случае наиболее универсальным и производительным решением является использование запроса с конструкцией ВЫБРАТЬ ПЕРВЫЕ 1.

Посмотрим на пример универсальной серверной функции:


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

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

← На главную