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

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

В процессе разработки сложных систем на базе 1С:ERP или других конфигураций часто возникает задача синхронизации данных. Например, при изменении документа в одной организации нам нужно автоматически обновить его копию в другой. Основная проблема заключается в производительности: если документ просто перепроводится без изменения значимых данных, стандартные механизмы (такие как ВерсияДанных или регистрация на узлах планов обмена) все равно срабатывают, вызывая лишние ресурсоемкие операции. В этой статье мы подробно разберем, как определить, что в документе действительно что-то поменялось, и отсечь "холостые" записи.

Проблема стандартного реквизита ВерсияДанных

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

Решение №1: Сравнение состояния в подписке на события

Один из самых надежных и производительных способов — это программное сравнение текущего состояния объекта с теми данными, которые еще хранятся в базе данных. Реализуется это в подписке на событие ПередЗаписью.

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


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

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

Решение №2: Метод контрольных сумм (Хеширование)

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

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

Посмотрим на пример формирования строки для хеша:


Функция ПолучитьХешДанныхДокумента(ДокументОбъект)
    СтрокаДанных = "";
    // Добавляем реквизиты шапки
    СтрокаДанных = СтрокаДанных + СокрЛП(ДокументОбъект.Контрагент.УникальныйИдентификатор());
    СтрокаДанных = СтрокаДанных + Формат(ДокументОбъект.СуммаДокумента, "ЧГ=0");
    
    // Добавляем данные табличной части
    Для Каждого СтрокаТЧ Из ДокументОбъект.Товары Цикл
        СтрокаДанных = СтрокаДанных + СокрЛП(СтрокаТЧ.Номенклатура.УникальныйИдентификатор());
        СтрокаДанных = СтрокаДанных + Формат(СтрокаТЧ.Количество, "ЧГ=0");
    КонецЦикла;
    
    // Вычисляем MD5 (условно, через средства платформы или внешние компоненты)
    Хеширование = Новый ХешированиеДанных(АлгоритмХеширования.MD5);
    Хеширование.Добавить(СтрокаДанных);
    Возврат Хеширование.ХешСумма;
КонецФункции

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

Решение №3: Сериализация в XML или JSON

Как предлагалось в сообщении №31, можно использовать сериализацию объекта. Этот метод хорош своей универсальностью, так как он автоматически учитывает все поля документа. Рассмотрим, как это работает на практике. Мы выгружаем объект в строку и сравниваем её с предыдущим состоянием.

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


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

// Сравниваем ТекущееСостояние со строкой, сохраненной в базе данных

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

Решение №4: Оптимизация через планы обмена

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

ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, Источник.Ссылка);

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

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

Выясним, какой метод выбрать в зависимости от ситуации:

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

← На главную