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