Почему метод НайтиПоИдентификатору() возвращает Неопределено после перемещения строк и записи документа

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

В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью идентификации конкретных строк табличной части (ТЧ) управляемой формы в разных событийных процедурах. Одним из стандартных способов является использование методов ПолучитьИдентификатор() и НайтиПоИдентификатору(). Однако, как показывает практика, этот механизм имеет свои «подводные камни», особенно когда речь идет о последовательности действий «Сдвиг строки — Запись документа».

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

Проанализируем проблему: если пользователь просто редактирует данные и нажимает «Записать», все работает корректно. Но если пользователь решит изменить порядок строк (сдвинуть строку вверх или вниз) перед сохранением, метод НайтиПоИдентификатору() внезапно начинает возвращать Неопределено. Разберемся, почему это происходит и как выстроить надежную архитектуру решения.

Природа временных идентификаторов управляемой формы

Важно понимать, что идентификатор, получаемый методом ПолучитьИдентификатор() для объекта типа ДанныеФормыЭлементКоллекции, является временным. Он существует только в рамках жизненного цикла текущего состояния данных в оперативной памяти клиента и сервера.

Когда мы вызываем команду «Записать», платформа выполняет сложный цикл трансформации данных:

  1. Данные из ДанныеФормы на клиенте передаются на сервер.
  2. На сервере они конвертируются в программный объект (ДокументОбъект).
  3. Выполняется транзакция записи в базу данных.
  4. После успешной записи объект конвертируется обратно в ДанныеФормы для отображения актуального состояния в интерфейсе.

В момент обратной конвертации (пункт 4) платформа пытается сопоставить строки объекта со строками, которые были в форме до записи. Если структура коллекции не менялась, платформа сохраняет старые идентификаторы. Однако интерактивный сдвиг строки (команда «Переместить вверх/вниз») меняет индекс строки и помечает коллекцию как «структурно измененную». При записи такого «грязного» состояния платформа часто сбрасывает старые идентификаторы и генерирует новые, из-за чего поиск по старому индексу (идентификатору) проваливается.

Разбор программного кода и ошибочного поведения

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


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

Затем на сервере выполняется попытка восстановить эти данные:


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

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

Решение №1: Использование номера строки (простой путь)

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

Проанализируем изменения в коде:

  1. В процедуре ПередЗаписью сохраняем не ПолучитьИдентификатор(), а НомерСтроки.
  2. При обработке на сервере используем метод НайтиСтроки() с отбором по номеру или просто перебор коллекции.

Однако помните: номер строки может измениться, если между клиентом и сервером происходит сортировка или программная вставка строк.

Решение №2: Использование скрытого ключа строки (профессиональный путь)

Для создания максимально отказоустойчивой системы рекомендуется добавить в табличную часть дополнительный реквизит, например, КлючСтроки с типом УникальныйИдентификатор или Строка. Этот реквизит будет служить «якорем», который не зависит ни от индексов платформы, ни от порядка строк в ТЧ.

Разберем алгоритм реализации по шагам:

Шаг 1. Генерация ключа. В событии ПриИзменении реквизитов строки или при добавлении строки проверяйте наличие ключа и заполняйте его, если он пуст:


&НаКлиенте
Процедура ХарактеристикиПриИзменении(Элемент)
    ТекущаяСтрока = Элементы.Характеристики.ТекущиеДанные;
    Если ТекущаяСтрока <> Неопределено И ПустаяСтрока(ТекущаяСтрока.КлючСтроки) Тогда
        ТекущаяСтрока.КлючСтроки = Строка(Новый УникальныйИдентификатор());
    КонецЕсли;
КонецПроцедуры

Шаг 2. Сохранение списка ключей. В процедуре ПередЗаписью сохраняйте именно эти ключи:


Для Каждого СтрокаТЧ Из МассивИзмененныхСтрок Цикл
    СписокКлючей.Добавить(СтрокаТЧ.КлючСтроки);
КонецЦикла;

Шаг 3. Поиск после записи. Теперь, даже если строки перемещались, удалялись или сортировались, вы всегда найдете нужную строку поиском по значению в данных:


Для Каждого Ключ Из СписокКлючей Цикл
    ПараметрыОтбора = Новый Структура("КлючСтроки", Ключ);
    НайденныеСтроки = Объект.Характеристики.НайтиСтроки(ПараметрыОтбора);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        СтрокаТЧ = НайденныеСтроки[0];
        // Выполняем действия
    КонецЕсли;
КонецЦикла;

Дополнительные рекомендации: работа с ссылкой нового объекта

В обсуждении часто упоминается метод ПолучитьСсылкуНового(). Выясним его роль. Этот метод полезен, когда вам нужно передать ссылку на еще не созданный документ в подчиненные объекты (например, записывая данные в справочник в момент ПередЗаписью документа на сервере).

Если вы столкнулись с тем, что ссылка документа еще не сформирована, используйте следующую конструкцию в модуле объекта:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если ЭтоНовый() Тогда
        СсылкаНаДокумент = ПолучитьСсылкуНового();
        Если СсылкаНаДокумент.Пустая() Тогда
            СсылкаНаДокумент = Документы.НазваниеВашегоДокумента.УстановитьСсылкуНового();
        КонецЕсли;
    Иначе
        СсылкаНаДокумент = Ссылка;
    КонецЕсли;
    
    // Теперь СсылкаНаДокумент можно передавать в другие методы
КонецПроцедуры

Помните, что УстановитьСсылкуНового() работает только для новых объектов. Для существующих объектов всегда используйте Объект.Ссылка.

Выводы

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

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

← На главную