Как отловить изменения в табличной части объекта 1С при записи

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

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

Основной принцип: сравнение объекта и ссылки

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

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

Способ 1: Простой перебор строк и сравнение полей

Рассмотрим базовый алгоритм, который подойдет для небольших табличных частей. Суть заключается в последовательном сравнении каждой строки текущего объекта со строкой из базы по тому же индексу. Проанализируем пример кода, который часто используют разработчики:


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

Важное замечание: Этот метод чувствителен к порядку строк. Если пользователь просто поменял две строки местами, алгоритм вернет Истина, так как значения в строках под конкретными индексами перестанут совпадать. Если порядок строк не важен, перед сравнением обе таблицы стоит отсортировать по ключевым полям.

Способ 2: Универсальная функция сравнения через метаданные

Если нам нужно проверять изменения часто и в разных объектах, логично написать универсальную функцию, которая сама определит состав колонок ТЧ через метаданные. Разберем такой пример:


Функция ПолучитьТекстРасхождений(Объект1, СсылкаНаОбъект2) Экспорт
    стрРезультат = "";
    
    МетаданныеОбъекта = Объект1.Метаданные();
    
    Для каждого МетаТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
        ИмяТЧ = МетаТЧ.Имя;
        ТЧ_Текущая = Объект1[ИмяТЧ];
        ТЧ_Базовая = СсылкаНаОбъект2[ИмяТЧ];
        
        // Сравниваем количество
        Если ТЧ_Текущая.Количество() <> ТЧ_Базовая.Количество() Тогда
            стрРезультат = стрРезультат + "Изменено количество строк в ТЧ " + ИмяТЧ + Символы.ПС;
        КонецЕсли;
        
        // Сравниваем содержимое строк (в пределах минимального количества)
        МинКоличество = Мин(ТЧ_Текущая.Количество(), ТЧ_Базовая.Количество());
        Для к = 0 По МинКоличество - 1 Цикл
            Для каждого Реквизит Из МетаТЧ.Реквизиты Цикл
                ИмяРеквизита = Реквизит.Имя;
                Если ТЧ_Текущая[к][ИмяРеквизита] <> ТЧ_Базовая[к][ИмяРеквизита] Тогда
                    стрРезультат = стрРезультат + СтрШаблон("ТЧ %1[%2].%3 изменено!", ИмяТЧ, к, ИмяРеквизита) + Символы.ПС;
                КонецЕсли; 
            КонецЦикла; 
        КонецЦикла; 
    КонецЦикла; 
    
    Возврат стрРезультат;
КонецФункции

Способ 3: Использование запроса (для высокой производительности)

Проанализируем ситуацию, когда табличная часть содержит тысячи строк. Перебор в цикле и постоянное обращение к Ссылка.ТЧ[к] может привести к существенным задержкам. В этом случае эффективнее использовать запрос.

Выясним причину эффективности: запрос выполняется на стороне сервера БД и позволяет одним махом сравнить данные. Мы можем выгрузить текущую ТЧ в таблицу значений, передать её в запрос как параметр и соединить с реальной таблицей базы данных по всем значимым полям.

  1. Выгружаем ТЧ объекта в ТаблицаЗначений.
  2. Передаем её в запрос через МенеджерВременныхТаблиц.
  3. В запросе делаем ПОЛНОЕ СОЕДИНЕНИЕ временной таблицы и таблицы из БД (например, Справочник.Контрагенты.КонтактнаяИнформация).
  4. Если в результате соединения есть строки, где одна из сторон содержит NULL или значения не равны — ТЧ изменена.

Особенности работы с контактной информацией

Посмотрим на пример из обсуждения, где фигурирует КонтактнаяИнформация. В типовых решениях (БСП) эта ТЧ обрабатывается особым образом. Если ваша конфигурация основана на БСП, мы рекомендуем использовать встроенные механизмы:

Архитектурный совет: Передача факта изменений из формы в объект

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

Рассмотрим алгоритм:

  1. В модуле формы в событии ПередЗаписьюНаСервере мы проверяем модифицированность или сравниваем данные.
  2. Если изменения есть, пишем в объект: ТекущийОбъект.ДополнительныеСвойства.Вставить("ТЧ_Изменена", Истина);.
  3. В модуле объекта в ПередЗаписью просто проверяем: Если ДополнительныеСвойства.Свойство("ТЧ_Изменена") Тогда ....

Этот подход позволяет избежать повторного тяжелого сравнения данных, если на форме изменения уже были зафиксированы стандартными средствами платформы (например, через свойство Модифицированность).

Подведем итог: Для простых задач достаточно построчного сравнения текущего объекта и его ссылки в процедуре ПередЗаписью. Для сложных систем лучше использовать универсальные функции на базе метаданных или запросы для оптимизации скорости работы. При использовании БСП всегда проверяйте наличие готовых функций сравнения в общих модулях ОбщегоНазначения.

← На главную