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