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

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

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

Механика сравнения: Текущий объект против данных в базе

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

Разберем базовый принцип: если мы обратимся к реквизиту напрямую (например, Наименование), мы получим то значение, которое пользователь только что ввел на форме. Если же мы обратимся через ссылку (Ссылка.Наименование), платформа выполнит запрос к базе данных и вернет то значение, которое было сохранено до начала текущей транзакции записи.

Рассмотрим простейший пример кода в модуле объекта:


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

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

Универсальный обход реквизитов через метаданные

Если нам нужно отследить изменения не в одном конкретном поле, а во всем справочнике сразу, писать условия для каждого реквизита вручную — неэффективно. Проанализируем, как можно автоматизировать этот процесс с помощью коллекции метаданных. Мы можем перебрать все реквизиты, определенные для данного объекта, и сравнить их значения динамически.

Рассмотрим пример универсального цикла:


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

    // Получаем метаданные текущего объекта
    МетаданныеОбъекта = ЭтотОбъект.Метаданные();
    
    // Перебираем все реквизиты, указанные в конфигураторе
    Для Каждого МДРеквизит Из МетаданныеОбъекта.Реквизиты Цикл
        ИмяРеквизита = МДРеквизит.Имя;
        
        СтароеЗначение = Ссылка[ИмяРеквизита];
        НовоеЗначение  = ЭтотОбъект[ИмяРеквизита];
        
        Если СтароеЗначение <> НовоеЗначение Тогда
            // Здесь фиксируем изменение
            ЗафиксироватьИзменение(ИмяРеквизита, СтароеЗначение, НовоеЗначение);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

Обратите внимание на использование синтаксиса квадратных скобок ЭтотОбъект[ИмяРеквизита]. Это позволяет обращаться к свойствам объекта по строковому имени, что делает код гибким и независимым от конкретного набора полей справочника.

Оптимизация производительности: Кэширование и запросы

При использовании подхода Ссылка.ИмяРеквизита внутри цикла, нужно учитывать важный нюанс производительности. Платформа 1С при каждом таком обращении может инициировать отдельное обращение к базе данных, если объект «тяжелый» или если вы обращаетесь к большому количеству полей. Это существенно замедляет запись, особенно при массовых обработках.

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

Разберем пример с использованием запроса для получения старых данных:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ИЗ Справочник.Контрагенты ГДЕ Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();

Если Выборка.Следующий() Тогда
    Для Каждого МДРеквизит Из Метаданные.Справочники.Контрагенты.Реквизиты Цикл
        Имя = МДРеквизит.Имя;
        Если ЭтотОбъект[Имя] <> Выборка[Имя] Тогда
            // Логика обработки изменений
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Этот метод гарантирует, что мы обращаемся к базе данных ровно один раз, независимо от количества проверяемых реквизитов.

Использование структуры ДополнительныеСвойства

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

Проанализируем алгоритм работы:

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

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

Учет специфики фоновых заданий и режима загрузки

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

  1. Обмен данными: Если свойство ОбменДанными.Загрузка установлено в Истина, это означает, что данные записываются из обмена или внешней обработки. В большинстве случаев в этом режиме сложную логику сравнения и ведения логов стоит отключать, чтобы избежать конфликтов и замедления импорта.
  2. Фоновые задания: В фоновых заданиях нет активного окна формы и текущего пользователя интерфейса. Чтобы понять, кто внес изменения, необходимо использовать функцию ПараметрыСеанса.ТекущийПользователь, предварительно убедившись, что параметры сеанса инициализированы.
  3. Табличные части: Сравнение табличных частей — более сложная задача. Прямое сравнение ЭтотОбъект.Товары = Ссылка.Товары не сработает. Потребуется выгрузить обе коллекции в таблицы значений и сравнить их построчно или с помощью запроса.

Типовое версионирование против собственного решения

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

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

Итоговые рекомендации

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

  1. Всегда проверяйте ЭтоНовый(), чтобы не сравнивать пустые ссылки.
  2. Используйте Метаданные() для создания универсального кода, который не сломается при добавлении новых реквизитов в конфигураторе.
  3. Для массовой проверки используйте один запрос к БД вместо множественных обращений через точку от Ссылка.
  4. Передавайте результаты анализа из ПередЗаписью в ПриЗаписи через ДополнительныеСвойства для обеспечения транзакционной безопасности.
  5. Всегда проверяйте ОбменДанными.Загрузка, чтобы не блокировать интеграционные процессы.
← На главную