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