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