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