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