При программной работе с табличными частями справочников и документов разработчики часто сталкиваются с ошибкой runtime-выполнения: «Поле объекта не обнаружено (ИмяПоля)». Рассмотрим подробно, почему возникает эта проблема, проанализируем ошибки в коде и разберем правильные способы обращения к коллекциям и строкам табличных частей.
Выясним причину возникновения ошибки на распространенном примере. Предположим, в справочнике создана табличная часть Сырье с реквизитами Сырье, Количество и Сумма.
Частая ошибка начинающих разработчиков выглядит следующим образом:
ДоговорЗаполнить.Сырье.Очистить();
ДоговорЗаполнить.Сырье.Добавить();
ДоговорЗаполнить.Сырье.Сырье = ВыборкаСубконтоКт1.СубконтоКт1; // Ошибка!
ДоговорЗаполнить.Сырье.Количество = ВыборкаСубконтоКт1.КоличествоКт;
В данном случае вызов метода Добавить() у объекта табличной части создает новую строку, но не присваивает её отдельной переменной. В следующей строке кода происходит обращение ДоговорЗаполнить.Сырье.Сырье. Поликлиника объекта ДоговорЗаполнить.Сырье — это коллекция строк (объект типа ТабличнаяЧасть), а не отдельная строка. У самой коллекции нет реквизитов Сырье, Количество или Сумма, они есть только у элементов коллекций (типа СтрокаТабличнойЧасти). Платформа 1С не может найти указанный реквизит у коллекции и генерирует исключение.
Чтобы правильно заполнить табличную часть, метод Добавить() должен возвращать ссылку на созданную строку в переменную. Через эту переменную выполняется заполнение реквизитов конкретной строки.
Разберем правильный вариант написания кода:
// Очищаем табличную часть перед заполнением
ДоговорЗаполнить.Сырье.Очистить();
// Добавляем новую строку и получаем ссылку на нее
НоваяСтрока = ДоговорЗаполнить.Сырье.Добавить();
// Заполняем реквизиты конкретной строки
НоваяСтрока.Сырье = ВыборкаСубконтоКт1.СубконтоКт1;
НоваяСтрока.Количество = ВыборкаСубконтоКт1.КоличествоКт;
НоваяСтрока.Сумма = ВыборкаСубконтоКт1.Сумма;
Именование реквизита табличной части точно так же, как и сама табличная часть (например, табличная часть Сырье и реквизит Сырье внутри нее), считается нежелательной практикой. Это затрудняет чтение кода и может вызывать путаницу при обращении к свойствам объекта. Рекомендуется давать табличной части имя во множественном числе (СырьеИМатериалы или Материалы), а реквизиту — в единственном (Номенклатура или Сырье).
Если синтаксис работы с табличной частью исправлен, но ошибка продолжается, проанализируем следующие возможные факторы:
Договоры, а в коде используется обращение к Справочники.ДоговорыКонтрагентов, метод ПолучитьОбъект() вернет объект конфигурации, в котором данная табличная часть отсутствует.Неопределено). Если метод поиска (например, НайтиПоКоду()) не находит элемент в базе, он возвращает пустую ссылку. Вызов ПолучитьОбъект() у пустой ссылки возвращает значение Неопределено. Любая попытка обратиться к свойству переменной со значением Неопределено вызывает аналогичную ошибку. Всегда проверяйте результат поиска перед считыванием объекта:
СсылкаНаДоговор = Справочники.ДоговорыКонтрагентов.НайтиПоКоду(КодДоговора);
Если ЗначениеЗаполнено(СсылкаНаДоговор) Тогда
ДоговорОбъект = СсылкаНаДоговор.ПолучитьОбъект();
// Работа с табличной частью
Иначе
// Обработка отсутствия элемента
КонецЕсли;
/ClearCache или очистите локальный кэш пользователя.Посмотрим на частую архитектурную ошибку: выполнение операций ПолучитьОбъект() и Записать() внутри цикла выборки. Считывание и запись объекта справочника на каждой итерации цикла существенно снижают производительность системы.
Правильный подход заключается в том, чтобы получить объект один раз до цикла (или по мере смены элемента), выполнить все необходимые манипуляции с табличной частью внутри цикла и записать объект один раз после завершения обработки:
// Получаем объект справочника один раз
ДоговорОбъект = СсылкаНаДоговор.ПолучитьОбъект();
ДоговорОбъект.Сырье.Очистить();
Пока Выборка.Следующий() Цикл
СтрокаТЧ = ДоговорОбъект.Сырье.Добавить();
СтрокаТЧ.Сырье = Выборка.СубконтоКт1;
СтрокаТЧ.Количество = Выборка.КоличествоКт;
СтрокаТЧ.Сумма = Выборка.Сумма;
КонецЦикла;
// Записываем объект один раз после заполнения
ДоговорОбъект.Записать();