При работе в конфигурациях на платформе 1С:Предприятие 8 часто возникает ситуация, когда привычные бизнес-процессы, такие как открытие формы заказа клиента, внезапно прерываются критической ошибкой: «Значение не является значением объектного типа». В данном материале мы подробно разберем, почему возникает эта ошибка на примере обращения к реквизиту ФизическоеЛицо, проанализируем некорректный программный код и выработаем надежные методы решения проблемы.
Ошибка «Значение не является значением объектного типа (ИмяРеквизита)» в языке 1С означает, что программный код пытается обратиться «через точку» к свойству объекта, который на самом деле не является объектом или ссылкой. В нашем случае система ожидает получить ссылку на справочник Пользователи, чтобы прочитать его реквизит ФизическоеЛицо, но вместо этого получает значение Неопределено или пустую ссылку.
Рассмотрим проблемный участок кода:
запрос.УстановитьПараметр("Менеджер", ОбщегоНазначенияПовтИсп.ПолучитьМенеджера().ФизическоеЛицо);
Если функция ПолучитьМенеджера() не находит подходящего сотрудника и возвращает «пустоту», попытка обращения к .ФизическоеЛицо мгновенно приводит к аварийному завершению работы формы. Выясним, почему функция может возвращать некорректный результат.
Проанализируем текст запроса, который используется внутри функции получения менеджера:
ВЫБРАТЬ ПЕРВЫЕ 1
Пользователи.Ссылка
ИЗ
Справочник.Пользователи КАК Пользователи
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ФизическиеЛица КАК ФизическиеЛица
ПО Пользователи.ФизическоеЛицо = ФизическиеЛица.Ссылка
ГДЕ
ФизическиеЛица.Должность = ЗНАЧЕНИЕ(Справочник.ДолжностиСотрудников.РозничныйМенеджерПоПродажеРасходныхМатериалов)
Здесь кроется первая и самая важная архитектурная ошибка. Несмотря на использование ЛЕВОЕ СОЕДИНЕНИЕ, условие в секции ГДЕ по правой таблице (ФизическиеЛица) фактически превращает соединение во Внутреннее (INNER JOIN). Если у пользователя не заполнено физическое лицо или у этого лица не указана в точности та должность, которая зашита в коде через ЗНАЧЕНИЕ(), запрос вернет пустую выборку.
Рассмотрим основные причины, по которым запрос внезапно «перестал работать»:
ФизическиеЛица, запрос не увидит нужную запись, даже если она физически существует в базе.Чтобы избежать падения системы, нам необходимо добавить проверку на наличие данных в выборке. Никогда нельзя обращаться к результату запроса напрямую, не убедившись, что метод Следующий() вернул Истина. Перепишем функцию правильно:
Функция ПолучитьМенеджера() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Пользователи.Ссылка КАК Ссылка
|ИЗ
| Справочник.Пользователи КАК Пользователи
|ГДЕ
| Пользователи.ФизическоеЛицо.Должность = &Должность";
Запрос.УстановитьПараметр("Должность", Справочники.ДолжностиСотрудников.РозничныйМенеджерПоПродажеРасходныхМатериалов);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Ссылка;
КонецЕсли;
// Если ничего не нашли, возвращаем пустую ссылку правильного типа
Возврат Справочники.Пользователи.ПустаяСсылка();
КонецФункции
Обратите внимание: мы убрали соединение таблиц и использовали обращение через точку в условии запроса (Пользователи.ФизическоеЛицо.Должность). В языке запросов 1С это безопасно и работает как неявное левое соединение.
Даже если функция возвращает пустую ссылку, обращение к ней через точку в параметрах другого запроса (как в исходном сообщении) все равно может вызвать проблемы, хотя для пустых ссылок это чаще возвращает NULL в базе, а не ошибку в коде. Тем не менее, хорошим тоном считается использование методов общего назначения.
Рассмотрим, как сделать вызов более защищенным:
МенеджерСсылка = ОбщегоНазначенияПовтИсп.ПолучитьМенеджера();
// Проверяем, что ссылка не пустая, прежде чем лезть в её реквизиты
Если ЗначениеЗаполнено(МенеджерСсылка) Тогда
ФизЛицо = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(МенеджерСсылка, "ФизическоеЛицо");
Запрос.УстановитьПараметр("Менеджер", ФизЛицо);
Иначе
// Обработка ситуации, когда менеджер не найден
Запрос.УстановитьПараметр("Менеджер", Справочники.ФизическиеЛица.ПустаяСсылка());
КонецЕсли;
Использование метода ОбщегоНазначения.ЗначениеРеквизитаОбъекта() предпочтительнее, так как оно работает быстрее (не читает весь объект целиком) и более устойчиво к ошибкам структуры данных.
Если вы столкнулись с подобной проблемой, выполните следующие шаги для локализации причины:
Пользователи, ФизическиеЛица или Должности.ПолучитьМенеджера() и посмотрите, что возвращает выборка.Следующий(). В оригинальном коде автора отсутствовала проверка этого условия, что и привело к обращению к пустому результату.Таким образом, для устранения ошибки необходимо обеспечить «защитное программирование»: всегда проверять успешность выполнения запроса, избегать жесткой привязки к конкретным элементам справочников через ЗНАЧЕНИЕ() (лучше использовать параметры или настройки программы) и корректно обрабатывать ситуации, когда данные в базе отсутствуют.