Как реализовать механизм проверки изменения объекта перед записью для оптимизации обмена данными?

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

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

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

Проблема стандартного метода «Модифицированность»

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

Решение 1: Сравнение «Объект — Ссылка» в обработчике ПередЗаписью

Самый простой и эффективный способ проверить изменения — сравнить текущие данные в оперативной памяти (объект) с теми данными, которые еще хранятся в базе данных (доступны по ссылке). Рассмотрим пример кода, который можно разместить в модуле формы или в модуле объекта.

Рассмотрим реализацию в модуле формы в процедуре ПередЗаписью:


&НаСервере
Функция ДанныеИзменились()
    
    // Если объект новый, то он априори изменен
    Если Объект.Ссылка.Пустая() Тогда
        Возврат Истина;
    КонецЕсли;
    
    // Получаем текущее состояние из БД по ссылке
    СтарыеДанные = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Объект.Ссылка, "Наименование, Код, Родитель, Склад");
    
    // Сравниваем интересующие нас поля
    Если Объект.Наименование <> СтарыеДанные.Наименование 
        Или Объект.Код <> СтарыеДанные.Код
        Или Объект.Родитель <> СтарыеДанные.Родитель
        Или Объект.Склад <> СтарыеДанные.Склад Тогда
        
        Возврат Истина;
    КонецЕсли;
    
    Возврат Ложь;
    
КонецФункции

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

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

Решение 2: Сравнение табличных частей

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

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


// В модуле объекта
ТЗ_Текущая = ЭтотОбъект.Товары.Выгрузить();
ТЗ_Старая = ЭтотОбъект.Ссылка.Товары.Выгрузить();

// Используем метод БСП для сравнения таблиц
Если Не ОбщегоНазначения.ТаблицыЗначенийИдентичны(ТЗ_Текущая, ТЗ_Старая) Тогда
    // Табличная часть изменилась
КонецЕсли;

Если БСП в конфигурации отсутствует, придется вручную сравнивать колонки в цикле или использовать свертку по ключевым полям и проверку итогов, но метод ТаблицыЗначенийИдентичны является наиболее надежным.

Решение 3: Передача признака изменения в подписки (ДополнительныеСвойства)

Часто проблема не в самой записи, а в том, что после записи срабатывает Подписка на событие, которая регистрирует объект для обмена. Чтобы точечно управлять этим процессом, воспользуемся структурой ДополнительныеСвойства.

Разберем по шагам:

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

Пример кода в модуле объекта:


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

Теперь в процедуре подписки на событие (где происходит ПланыОбмена.ЗарегистрироватьИзменения) мы можем прочитать это свойство:


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

Решение 4: Сериализация как универсальный метод

Если нужно проверить изменение абсолютно всех полей объекта, включая дополнительные реквизиты и все табличные части, можно воспользоваться методом сериализации в JSON или XML. Мы превращаем текущий объект в строку и объект из базы в строку, а затем сравниваем две строки.

Рассмотрим пример с JSON:


ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ЭтотОбъект);
СтрокаТекущая = ЗаписьJSON.Закрыть();

ЗаписьJSON_БД = Новый ЗаписьJSON;
ЗаписьJSON_БД.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON_БД, ЭтотОбъект.Ссылка.ПолучитьОбъект());
СтрокаБД = ЗаписьJSON_БД.Закрыть();

Если СтрокаТекущая <> СтрокаБД Тогда
    // Объект изменился целиком
КонецЕсли;

Важный момент: этот метод достаточно ресурсоемкий. Его стоит применять только для небольших объектов или в тех случаях, когда ручное перечисление всех реквизитов невозможно (например, в универсальных обработках).

Рекомендации по реализации

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

Использование предложенных подходов позволит вам сделать систему более отзывчивой и значительно снизить нагрузку на каналы обмена данными за счет исключения «пустых» транзакций.

← На главную