Как быстро и надежно сравнить объекты 1С для выявления изменений?

Программист 1С v8.3 (Управляемые формы)
← К списку

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

1. Использование свойств "Модифицированность" и "ВерсияДанных"

Начнем с самых простых и встроенных механизмов, которые предоставляет платформа 1С.

Свойство Модифицированность

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

Пример использования:

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

Однако, у Модифицированность есть свои ограничения:

  1. Оно реагирует на любые изменения, даже если они затем были отменены, или на изменения, которые не влияют на "суть" объекта (например, изменение несущественного реквизита, который не должен влиять на логику обмена).
  2. Модифицированность не всегда устанавливается автоматически при изменении реквизитов объекта на сервере, особенно если изменение происходит программно вне прямого взаимодействия с элементами формы. В таких случаях может потребоваться принудительная установка Модифицированность = Истина.
  3. Это свойство не подходит для надежной регистрации изменений для обмена данными или версионирования, так как оно не предоставляет детальной информации о том, что именно изменилось, и может быть сброшено.

Свойство ВерсияДанных

Свойство ВерсияДанных объекта содержит уникальный идентификатор, который изменяется при каждой записи объекта. Оно может использоваться для быстрого определения факта изменения объекта, но не предоставляет информации о характере изменений.

Пример использования:

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

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

2. Сравнение через сериализацию (ЗаписьFastInfoset, ЗаписьJSON)

Перейдем к более надежным, но и более ресурсоемким методам. Использование сериализации объектов в форматы FastInfoset или JSON для последующего сравнения их контрольных сумм или самих сериализованных представлений является распространенным подходом для определения изменений.

Принцип работы:

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

Производительность ЗаписьFastInfoset и ЗаписьJSON:

В общем случае, производительность сериализации может варьироваться. Для однократной сериализации JSON может быть немного быстрее, но при многократных операциях сериализации XML (и, вероятно, FastInfoset как его бинарный аналог) может показать лучшую производительность из-за возможного кэширования или особенностей реализации библиотеки.

Надежность сравнения через сериализацию:

Сериализация объекта в строку или бинарные данные, а затем сравнение этих данных или их хешей, позволяет надежно определить, были ли изменены свойства объекта. Это особенно полезно для сложных объектов со множеством вложенных свойств (табличных частей, реквизитов-ссылок).

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

Применение:

Этот подход хорошо подходит для регистрации изменений в объектах для обмена со сторонними информационными системами или для версионирования, когда необходимо отслеживать каждое изменение объекта.

3. Хеширование данных объекта (КонтрольнаяСумма)

Один из наиболее эффективных и гибких подходов — это хеширование данных объекта. Мы создаем уникальную "контрольную сумму" или хеш-значение на основе содержимого объекта. Даже небольшое изменение в объекте приведет к совершенно другому хешу. Сравнение хешей является очень эффективным способом быстрого определения, изменился ли объект.

Принцип работы:

Разберем по шагам, как это работает:

  1. Мы определяем, какие именно данные объекта являются значимыми для сравнения. Это ключевой момент, который позволяет избежать ложных срабатываний при изменении "незначащих" реквизитов (как, например, "ДатаОперации" в примере из форума).
  2. Эти значимые данные передаются в функцию хеширования.
  3. Функция хеширования генерирует уникальную строку (хеш-сумму).
  4. Мы сохраняем эту хеш-сумму (например, в регистре сведений) при первой записи объекта или при последнем успешном сравнении.
  5. При последующих проверках мы генерируем новый хеш для текущего состояния объекта и сравниваем его с сохраненным. Если хеши разные, объект был изменен.

Пример реализации функции хеширования и сравнения:

Рассмотрим пример кода, который был предложен в теме на форуме и адаптирован для нашего случая. Здесь используется объект ХешированиеДанных с алгоритмом MD5.

Сначала определим вспомогательную функцию КонтрольнаяСумма, которая будет принимать данные и возвращать их хеш:


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

Теперь рассмотрим функцию ВерсияОтличаетсяОтРанееЗаписанной, которая использует КонтрольнаяСумма для сравнения текущего объекта с его последней сохраненной версией:


// Функция: ВерсияОтличаетсяОтРанееЗаписанной
// Назначение: Определяет, отличается ли текущая версия объекта от последней записанной.
// Параметры:
//   Объект - Объект базы данных (например, ДокументОбъект, СправочникОбъект).
// Возвращаемое значение: Булево - Истина, если версия отличается, Ложь - в противном случае.
Функция ВерсияОтличаетсяОтРанееЗаписанной(Объект)
	
	// Для хранения контрольной суммы и других данных версии объекта нам потребуется
	// специальный регистр сведений, например, "ВерсииОбъектов".
	// Он должен содержать измерения: Объект (Ссылка на версионируемый объект),
	// и ресурсы: КонтрольнаяСумма (Строка), НомерВерсии (Число).
	ТекстЗапроса = 
	"ВЫБРАТЬ ПЕРВЫЕ 1
	|	ВерсииОбъектов.КонтрольнаяСумма
	|ИЗ
	|	РегистрСведений.ВерсииОбъектов КАК ВерсииОбъектов
	|ГДЕ
	|	ВерсииОбъектов.Объект = &Объект
	|
	|УПОРЯДОЧИТЬ ПО
	|	ВерсииОбъектов.НомерВерсии УБЫВ"; // Получаем последнюю версию
	
	Запрос = Новый Запрос(ТекстЗапроса);
	Запрос.УстановитьПараметр("Объект", Объект.Ссылка);
	Выборка = Запрос.Выполнить().Выбрать();
	
	// Определяем текущую контрольную сумму объекта
	// Здесь нам потребуется функция ДанныеДляХранения(Объект), которая будет
	// формировать структуру или строку из значимых реквизитов объекта.
	// Например:
	// ТекущиеДанныеДляХеширования = Новый Структура;
	// ТекущиеДанныеДляХеширования.Вставить("Объект", Объект); // Или только значимые реквизиты
	// ТекущиеДанныеДляХеширования.Вставить("ДополнительныеРеквизиты", Объект.ДополнительныеРеквизиты);
	// ТекущаяКонтрольнаяСумма = КонтрольнаяСумма(ТекущиеДанныеДляХеширования);
	// Для примера используем упрощенный вариант, предполагая, что ДанныеДляХранения
	// уже возвращает корректный объект для хеширования.
	
	// В реальной ситуации функция ДанныеДляХранения(Объект) должна быть реализована,
	// чтобы собрать все значимые для сравнения реквизиты объекта.
	// Например, она может сериализовать объект без служебных реквизитов или собрать структуру.
	// Для демонстрации мы будем использовать заглушку.
	// Пусть ТекущаяКонтрольнаяСумма будет результатом хеширования значимых данных объекта.
	// Примем, что ДанныеДляХранения(Объект) возвращает структуру или строку,
	// пригодную для функции КонтрольнаяСумма.
	// Например, это может быть сериализованный JSON-представление объекта,
	// из которого исключены незначимые поля.
	
	// *** Важно: Реализация ДанныеДляХранения(Объект) критична для точности сравнения.
	// Она должна возвращать только те данные, изменения которых мы хотим отслеживать.
	// Пример (упрощенный, для демонстрации):
	// Функция ДанныеДляХранения(Объект1С)
	//     СтруктураДанных = Новый Структура;
	//     СтруктураДанных.Вставить("Ссылка", Объект1С.Ссылка);
	//     СтруктураДанных.Вставить("Наименование", Объект1С.Наименование);
	//     СтруктураДанных.Вставить("Комментарий", Объект1С.Комментарий);
	//     // Добавьте сюда все значимые реквизиты, включая табличные части
	//     Возврат ОбщегоНазначения.ЗначениеВСтрокуXML(СтруктураДанных); // Сериализуем для консистентности
	// КонецФункции
	
	// Для примера используем заглушку, предполагая, что у нас есть такая функция.
	// В реальном коде вам нужно будет реализовать ДанныеДляХранения(Объект)
	// с учетом всех значимых реквизитов вашего объекта.
	// Например, можно получить объект, удалить служебные реквизиты и сериализовать.
	// ТекущаяКонтрольнаяСумма = КонтрольнаяСумма(ДанныеДляХранения(Объект));
	// Для простоты примера, давайте предположим, что мы хешируем сам объект,
	// но помним, что это может быть не идеально для сложных объектов.
	ТекущаяКонтрольнаяСумма = КонтрольнаяСумма(ОбщегоНазначения.ЗначениеВСтрокуXML(Объект));
	
	Если Выборка.Следующий() И Не ПустаяСтрока(Выборка.КонтрольнаяСумма) Тогда
		// Если нашли предыдущую версию, сравниваем хеши.
		Возврат Выборка.КонтрольнаяСумма <> ТекущаяКонтрольнаяСумма;
	КонецЕсли;
	
	// Если версий не было или объект новый, сравниваем с эталонной версией.
	// Если объект новый, то он всегда отличается.
	// Если объект не новый, но версий не было (например, до включения версионирования),
	// то нужно сравнить текущий хеш с хешем сохраненного объекта из базы.
	Возврат Объект.ЭтоНовый() ИЛИ ТекущаяКонтрольнаяСумма <> КонтрольнаяСумма(ОбщегоНазначения.ЗначениеВСтрокуXML(Объект.Ссылка.ПолучитьОбъект()));
	
КонецФункции

Выбор алгоритма хеширования:

Хотя MD5 является распространенным алгоритмом, для сценариев, где важна криптографическая стойкость (например, защита от преднамеренной подделки), рекомендуется использовать более стойкие алгоритмы, такие как SHA-256 или SHA-512, поскольку MD5 и SHA-1 известны своими уязвимостями к коллизиям. Однако для простого обнаружения изменений в объектах 1С, где нет прямого риска криптографических атак, MD5 обычно считается достаточным и может быть быстрее.

Что хешировать:

Мы выяснили, что важно определить, какие данные объекта должны быть включены в хеш. Исключение "незначащих" реквизитов (как "ДатаОперации" в примере из сообщения 9) позволяет избежать ложных срабатываний при изменении этих полей. Для сложных объектов может потребоваться предварительная сериализация данных в определенный формат (например, JSON или FastInfoset), а затем хеширование полученной строки или бинарных данных. Это обеспечивает консистентность представления данных для хеширования.

Производительность:

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

4. Механизм версионирования объектов в БСП

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

Механизм работы:

Стандартное версионирование в БСП часто использует сериализацию объектов, например, через ЗаписьFastInfoset, для сохранения полной версии объекта в специальном регистре сведений. При каждой записи объекта, если он изменился, создается новая запись в регистре, хранящая его актуальное состояние.

Влияние на производительность:

Мы должны понимать, что включение версионирования в БСП может значительно замедлять работу системы, особенно для часто изменяемых объектов. Каждая запись объекта приводит к созданию новой версии и дополнительным операциям ввода-вывода (запись в регистр сведений, сериализация). Это может привести к увеличению задержек, особенно при большом количестве версионированных объектов и активной работе пользователей.

Оптимизации и альтернативы:

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

5. Обнаружение изменений для обмена со сторонними системами

Когда речь идет об обмене данными со сторонними информационными системами, требования к обнаружению изменений могут быть специфичными.

Регистрация в планах обмена:

В типовых конфигурациях 1С (таких как ERP или Комплексная автоматизация) для регистрации изменений объектов, предназначенных для обмена, используются планы обмена. Объекты регистрируются в плане обмена при каждой записи, без необходимости сложных предварительных сравнений. Это простой и надежный механизм, который гарантирует, что любое изменение будет зафиксировано.

Использование хешей для контроля перед выгрузкой:

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

Рассмотрим, как это работает:

  1. При первой успешной выгрузке объекта мы вычисляем его хеш-сумму (используя подходы из раздела 3, исключая незначимые поля).
  2. Эту хеш-сумму мы храним в отдельном регистре сведений, привязанном к объекту и узлу обмена.
  3. При последующих выгрузках (например, в регламентном задании) мы сначала проверяем, зарегистрирован ли объект в плане обмена.
  4. Если объект зарегистрирован, мы вычисляем его текущий хеш и сравниваем с сохраненным.
  5. Если хеши совпадают, значит, значимых изменений нет, и мы можем не выгружать данные, удалив регистрацию из плана обмена. Это снижает нагрузку на систему обмена и уменьшает объем передаваемых данных.

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

Заключение

Мы рассмотрели несколько подходов к быстрому и надежному сравнению объектов в 1С. Выбор конкретного метода зависит от ваших задач:

Надеемся, этот подробный разбор поможет вам выбрать и реализовать оптимальное решение для вашей задачи!

← К списку