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