Совпадает ли номер строки на управляемой форме с номером строки в объекте 1С, и как правильно удалять строки табличной части?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с управляемыми формами в «1С:Предприятие 8» начинающие разработчики часто сталкиваются с неочевидным поведением табличных частей. Типичный сценарий: нужно программно удалить одну или несколько строк из табличной части (ТЧ) документа или справочника. Казалось бы, достаточно получить текущий номер строки на форме и по нему же выполнить удаление в объекте на сервере. Однако на практике такой подход неизбежно приводит к критическим ошибкам, когда у пользователя визуально выделена одна строка, а система удаляет совершенно другую.

В этой статье мы подробно разберем внутренние механизмы платформы «1С», выясним, почему номера строк на форме и в базе данных могут отличаться, и рассмотрим правильные шаблоны проектирования для безопасного удаления строк.

В чем коварство номера строки: НомерСтроки vs Индекс vs Идентификатор

Для того чтобы раз и навсегда защитить свой код от плавающих ошибок при работе с табличными частями, давайте детально проанализируем три ключевых понятия, которые использует платформа для адресации строк:

  1. Номер строки (НомерСтроки): это стандартный реквизит табличной части, значение которого начинается с 1. Он физически записывается в базу данных и отображает порядковое положение строки. Главная особенность в том, что этот номер динамически пересчитывается платформой в момент записи объекта.
  2. Индекс строки: это позиция строки в коллекции в оперативной памяти (индексация начинается с 0). Индексы крайне нестабильны. Если в табличной части из 5 строк мы удалим строку с индексом 0, то строка, которая только что имела индекс 1, мгновенно получит индекс 0. Именно поэтому прямое удаление в цикле «сверху вниз» по индексам всегда приводит к пропуску строк или выходу за пределы границы массива.
  3. Идентификатор строки: это уникальное числовое значение, которое платформа присваивает каждой строке табличной части при ее выводе на форму в рамках текущего сеанса. Идентификатор генерируется динамически, он абсолютно уникален в пределах таблицы на форме и никогда не изменяется при сортировке, фильтрации или удалении соседних строк.

Рассмотрим подробнее, почему использование свойства НомерСтроки или индекса для поиска строки на сервере — это опасное заблуждение.

Причина №1: Интерактивная сортировка и отборы на форме

Представим ситуацию: в табличной части справочника в базе данных хранится 5 строк. Пользователь открывает форму элемента и решает отсортировать данные, кликнув по заголовку колонки «Наименование», либо накладывает быстрый отбор по какому-то значению. Визуальный порядок строк на клиенте изменился. При этом в самом объекте (на сервере в оперативной памяти или в БД) порядок строк остается первоначальным до тех пор, пока данные не будут физически перезаписаны.

Если в этот момент мы попытаемся получить номер строки с формы и удалить строку с таким же номером в объекте, то удалим совершенно другие данные. Пользователь будет крайне удивлен, обнаружив пропажу не той информации, которую он выбрал.

Причина №2: Новые и незаписанные строки

Если пользователь в процессе работы добавил новые строки в табличную часть на форме, но еще не нажал кнопку «Записать», эти строки уже существуют в оперативной памяти формы. Однако в базе данных их еще нет. Пытаясь оперировать физическими номерами строк напрямую в БД, мы просто не найдем новые данные или нарушим целостность коллекции.

Причина №3: Коварство свойства «ИсходныйНомерСтроки»

Иногда разработчики пытаются использовать свойство ИсходныйНомерСтроки. Это свойство действительно хранит номер строки, каким он был в момент считывания объекта из базы данных. Но у него есть два критических недостатка:

Архитектурная ошибка: Удаление строк напрямую из базы данных при открытой форме

Очень часто программисты совершают грубую ошибку: пытаются получить ссылку на объект, открыть его на сервере через ПолучитьОбъект(), удалить строку там, записать объект в базу данных, а затем ожидать, что форма сама обновится.

Делать так категорически нельзя! Если на клиенте у пользователя открыта форма, в ней находится актуальная копия данных (в реквизите Объект). Если мы параллельно на сервере изменим этот же объект напрямую в базе данных, то при первой же попытке пользователя сохранить форму система выдаст критическую ошибку: «Данные были изменены или удалены другим пользователем». Произойдет конфликт версий данных, и пользователь потеряет всю введенную работу.

Золотое правило разработки в 1С: все изменения табличной части должны происходить в контексте данных формы (в реквизите Объект.ИмяТабличнойЧасти), а запись в базу данных должна выполняться штатно, при сохранении всей формы целиком.

Правильный шаблон удаления строк по идентификаторам

Для безопасной работы на управляемых формах свойство элемента формы ВыделенныеСтроки всегда возвращает массив идентификаторов строк, а не их индексы или номера. Чтобы получить конкретную строку табличной части по ее идентификатору, разработчики должны использовать специализированный метод НайтиПоИдентификатору().

Проанализируем правильный шаблон кода для удаления выделенных строк. Обратите внимание, что перед удалением строк в цикле необходимо выгрузить идентификаторы в отдельный массив. Если обходить коллекцию ВыделенныеСтроки напрямую и удалять элементы, состав коллекции будет меняться «на лету», что приведет к непредсказуемым ошибкам в цикле.


&НаКлиенте
Процедура УдалитьВыделенныеСтроки(Команда)

    // Шаг 1. Получаем элемент формы, связанный с нашей табличной частью
    ТаблицаФормы = Элементы.Задачи;
    
    // Шаг 2. Создаем вспомогательный массив для фиксации идентификаторов
    МассивИдентификаторов = Новый Массив;
    Для Каждого ИдентификаторСтроки Из ТаблицаФормы.ВыделенныеСтроки Цикл
        МассивИдентификаторов.Добавить(ИдентификаторСтроки);
    КонецЦикла;
    
    // Шаг 3. Безопасно удаляем строки по их идентификаторам
    Для Каждого ИдентификаторСтроки Из МассивИдентификаторов Цикл
        // Находим строку в данных формы (реквизит Объект) по ее уникальному ID
        СтрокаТЧ = Объект.Задачи.НайтиПоИдентификатору(ИдентификаторСтроки);
        Если СтрокаТЧ <> Неопределено Тогда
            Объект.Задачи.Удалить(СтрокаТЧ);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

Сложный сценарий: Удаление строки ТЧ и связанного объекта в БД

Разберем более сложную практическую задачу. Допустим, у нас есть табличная часть со списком связанных событий. При удалении строки из табличной части нам необходимо не просто убрать ее с формы, но и физически удалить (или пометить на удаление) связанный элемент справочника в базе данных, ссылка на который хранится в этой строке.

Решать такую задачу нужно через контекстный вызов сервера, передавая туда массив идентификаторов. Давайте разберем по шагам, как это реализовать без конфликта версий данных.


&НаКлиенте
Процедура УдалитьСтрокуИСвязанныйОбъект(Команда)
    
    ТаблицаФормы = Элементы.События;
    МассивИдентификаторов = Новый Массив;
    
    Для Каждого ИдентификаторСтроки Из ТаблицаФормы.ВыделенныеСтроки Цикл
        МассивИдентификаторов.Добавить(ИдентификаторСтроки);
    КонецЦикла;
    
    // Вызываем серверную процедуру для удаления данных
    УдалитьСтрокиНаСервере(МассивИдентификаторов);
    
КонецПроцедуры

&НаСервере
Процедура УдалитьСтрокиНаСервере(МассивИдентификаторов)
    
    // Массив для накопления ссылок на связанных объекты, которые нужно удалить в БД
    МассивОбъектовНаУдаление = Новый Массив;
    
    Для Каждого ИдентификаторСтроки Из МассивИдентификаторов Цикл
        
        // Ищем строку в контексте данных формы (в реквизите Объект)
        СтрокаТЧ = Объект.События.НайтиПоИдентификатору(ИдентификаторСтроки);
        
        Если СтрокаТЧ <> Неопределено Тогда
            // Если в строке заполнена ссылка на связанный справочник, запоминаем ее
            Если ЗначениеЗаполнено(СтрокаТЧ.СсылкаНаСобытие) Тогда
                МассивОбъектовНаУдаление.Добавить(СтрокаТЧ.СсылкаНаСобытие);
            КонецЕсли;
            
            // Удаляем строку из табличной части самой формы
            Объект.События.Удалить(СтрокаТЧ);
        КонецЕсли;
        
    КонецЦикла;
    
    // Теперь, когда данные формы скорректированы, безопасно удаляем связанные объекты из БД
    Для Каждого СсылкаНаОбъект Из МассивОбъектовНаУдаление Цикл
        Попытка
            // Получаем объект базы данных для удаления
            СправочникОбъект = СсылкаНаОбъект.ПолучитьОбъект();
            Если СправочникОбъект <> Неопределено Тогда
                // Помечаем на удаление (или вызываем СправочникОбъект.Удалить() для полного физического стирания)
                СправочникОбъект.УстановитьПометкуУдаления(Истина);
            КонецЕсли;
        Исключение
            ЗаписьЖурналаРегистрации("Удаление объектов", УровеньЖурналаРегистрации.Ошибка,,, 
                "Не удалось удалить связанный объект: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
        КонецПопытки;
    КонецЦикла;
    
    // Внимание: Сам объект формы (например, документ-владелец ТЧ) мы здесь НЕ записываем!
    // Он запишется стандартно, когда пользователь нажмет кнопку "Записать" на форме.
    
КонецПроцедуры

Подведем итоги

Мы выяснили, что визуальное отображение строк на форме и их реальный порядок в прикладном объекте в базе данных — это разные сущности. Никогда не полагайтесь на реквизит НомерСтроки при программной обработке ТЧ. Всегда используйте идентификаторы строк и метод НайтиПоИдентификатору(). Это гарантирует стабильную работу вашей конфигурации при любых интерактивных действиях пользователя, таких как сортировки, группировки и отборы.

← На главную