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

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

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

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

Суть проблемы и анализ исходной ошибки

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


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

При вызове этой функции система экстренно завершает работу с ошибкой:

{ОбщийМодуль.РаботаСоСправочниками.Модуль(5)}: Значение не является значением объектного типа (КодПечати)

Выясним причину такого поведения. Как указано в описании структуры метаданных, регистр сведений КодПечатиЯрлыка является непериодическим. Он содержит единственное измерение Номенклатура и ресурс КодПечати.

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

  1. Поскольку регистр не имеет разреза по времени (периода), концепция «последней» записи для него в принципе отсутствует.
  2. Менеджер непериодического регистра не может обработать данный вызов корректно и возвращает значение Неопределено вместо ожидаемой структуры с ресурсами.
  3. На следующей строке кода программа пытается обратиться к свойству КодПечати у переменной ЗначениеРесурсов, которая на данный момент равна Неопределено. Поскольку у примитивного типа Неопределено нет и не может быть никаких свойств, платформа генерирует исключение «Значение не является значением объектного типа».

Попытка автора исправить ситуацию в Сообщении 5 путем передачи даты в качестве первого параметра (ПолучитьПоследнее(АктуальнаяДата, Отбор)) закономерно привела к другой ошибке: Несоответствие типов (Параметр номер "1"). Это произошло по той же причине — менеджер непериодического регистра не ожидает на вход параметры, связанные со временем.

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

---

Решение 1. Использование стандартного метода Получить() — быстро и лаконично

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

Давайте перепишем исходную функцию автора, используя этот метод:


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

Этот способ обладает важными преимуществами:

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

---

Решение 2. Получение данных с помощью запроса — универсально и производительно

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

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


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

Обратите внимание на критическую деталь, упомянутую в Сообщении 9. В Сообщении 8 разработчик ошибочно попытался построить запрос к виртуальной таблице СрезПоследних для непериодического регистра. У непериодических регистров сведений нет виртуальных таблиц срезов. Запрос нужно делать непосредственно к физической таблице регистра (РегистрСведений.КодПечатиЯрлыка).

---

Решение 3. Использование объекта МенеджерЗаписи — для проверки физического существования записи

Иногда перед разработчиком стоит задача не просто получить дефолтное пустое значение (как это делает метод Получить()), а четко определить: существует ли физически запись в базе данных?

Для решения этой задачи идеально подходит объектный метод работы с данными через МенеджерЗаписи. Рассмотрим пример реализации:


Функция КодПечати(ЭлементНоменклатуры) Экспорт
    // Создаем пустой менеджер записи для нашего регистра
    МенеджерЗаписи = РегистрыСведений.КодПечатиЯрлыка.СоздатьМенеджерЗаписи();
    
    // Заполняем ключевые поля (измерения) для поиска
    МенеджерЗаписи.Номенклатура = ЭлементНоменклатуры;
    
    // Выполняем чтение записи из базы данных
    МенеджерЗаписи.Прочитать();
    
    // Проверяем, удалось ли прочитать существующую запись
    Если МенеджерЗаписи.Выбран() Тогда
        Возврат МенеджерЗаписи.КодПечати;
    Иначе
        // Записи с такой номенклатурой физически нет в базе данных
        Возврат Неопределено;
    КонецЕсли;
КонецФункции

Метод Выбран() возвращает Истина только в том случае, если запись действительно была найдена в СУБД. Это позволяет гибко ветвить логику программы в зависимости от наличия настроек в регистре.

---

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

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

  1. Одиночный вызов: функция вызывается при интерактивном нажатии кнопки пользователем на форме или при открытии карточки одного товара. В данном сценарии использование легких методов Получить() или МенеджерЗаписи полностью оправдано. Код отрабатывает мгновенно, а разработка занимает минимум времени.
  2. Вызов в цикле или при обработке массивов данных: функция вызывается внутри цикла (например, при проведении или заполнении табличной части крупного документа на 500-1000 строк). Использовать методы Получить() или МенеджерЗаписи в циклах категорически запрещено!

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

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

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


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

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

← На главную