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

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

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

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

Метод 1. Передача признака модифицированности из формы

Это один из самых «легких» и производительных способов. Форма 1С сама отслеживает состояние интерактивного изменения данных. Если мы работаем в контексте управляемой формы, нам не нужно заново вычислять изменения в модуле объекта — достаточно передать уже имеющееся состояние.

Рассмотрим алгоритм реализации:

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

Пример кода в модуле формы:


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

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


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

Важный нюанс: этот метод отлично работает при интерактивной записи, но бесполезен при программной записи объекта (например, из обработки), так как формы в этом случае не существует.

Метод 2. Универсальное сравнение через метаданные

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

Рассмотрим, как реализовать такую проверку в модуле объекта:


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

Проанализируем этот код: мы используем прямое обращение к реквизитам через ссылку (СсылкаНаОбъект[ИмяРеквизита]). Платформа 1С оптимизирует такие обращения, но при большом количестве реквизитов и строк в табличных частях это может замедлить запись. Также стоит помнить, что такой метод не учитывает тип ХранилищеЗначений, который нельзя сравнивать через оператор <>.

Метод 3. Использование инструментов БСП

Если ваша конфигурация построена на базе БСП (Библиотека Стандартных Подсистем), мы можем использовать готовые и оптимизированные механизмы. В модуле ОбщегоНазначения часто доступны функции сравнения коллекций и структур.

Разберем пример с использованием выгрузки в таблицу значений и сравнения через БСП:


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

Этот подход профессиональнее, так как ОбщегоНазначения.ЗначенияРеквизитовОбъекта выполняет один эффективный запрос к базе данных вместо множества обращений к полям ссылки. Метод КоллекцииИдентичны корректно обрабатывает различные типы данных.

Метод 4. Сравнение контрольных сумм (Хеширование)

Для очень крупных объектов с десятками табличных частей обход метаданных может быть избыточным. Проанализируем альтернативный вариант: расчет хеш-суммы объекта.

Рассмотрим шаги этого метода:

  1. Сериализуем текущий объект в строку XML или JSON.
  2. Сериализуем объект, прочитанный из базы по ссылке.
  3. Сравниваем полученные строки (или их хеши).

Посмотрим на пример логики:


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

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

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

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

Проблема технических реквизитов и "ложных" изменений

Выясним причину, по которой иногда «чистая» запись без изменений всё равно распознается как изменение. В реальных бизнес-приложениях существуют реквизиты, которые обновляются программно в коде модуля объекта:

Если вы используете методы сравнения, описанные выше, обязательно внедрите список исключений. Это массив имен реквизитов, изменение которых не должно инициировать вашу бизнес-логику «реального изменения». Проанализируем ситуацию: если пользователь не трогал данные, но программа обновила дату записи — технически объект изменился, но для пользователя он остался прежним.

Рекомендации по выбору метода

Подведем итоги и выберем оптимальный путь для вашего случая:

  1. Если задача только визуальная (например, спросить пользователя «Вы уверены?»), используйте ДополнительныеСвойства и передачу модифицированности из формы. Это самый быстрый способ, не нагружающий сервер.
  2. Для контроля в бизнес-логике (например, запрет перепроведения без правок), используйте метод с БСП (КоллекцииИдентичны) или ручной обход реквизитов, если БСП не подключена.
  3. Для систем версионирования и аудита лучше всего подходит сравнение через сериализацию, так как оно гарантирует, что ни одно поле (включая скрытые) не будет упущено.

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

← На главную