Как узнать, успешно ли записан элемент справочника или произошел отказ от записи?

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

При разработке механизмов интеграции 1С с внешними системами (например, базами данных MS SQL) программисты часто сталкиваются с дилеммой: в какой момент передавать данные во внешнюю базу? Если сделать это слишком рано (в событии ПередЗаписью), транзакция в 1С может откатиться из-за ошибки в других модулях, а во внешней базе изменения уже будут зафиксированы. Если делать это в самом конце события ПриЗаписи, всё равно остается риск, что сработает подписка на событие, расположенная ниже в дереве метаданных, которая установит флаг Отказ = Истина.

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

Понимание жизненного цикла транзакции записи

Для начала проанализируем последовательность событий при записи объекта. Когда мы вызываем метод Записать(), платформа открывает транзакцию. Внутри этой транзакции последовательно выполняются:

  1. Событие ПередЗаписью (в модуле объекта).
  2. Подписки на событие ПередЗаписью.
  3. Непосредственная запись данных в таблицы СУБД (физическая запись).
  4. Событие ПриЗаписи (в модуле объекта).
  5. Подписки на событие ПриЗаписи.
  6. Фиксация (Commit) транзакции.

Важно понимать: если на любом из этих этапов (даже в самой последней подписке) возникнет ошибка или будет установлен Отказ = Истина, вся транзакция откатится. Это означает, что любые изменения, внесенные в базу 1С в рамках этой транзакции, будут аннулированы. Однако, если вы внутри этой транзакции отправили запрос к стороннему API или выполнили хранимую процедуру в MS SQL, это действие не откатится автоматически вместе с транзакцией 1С.

Решение 1: Использование планов обмена (рекомендуемый метод)

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

План обмена позволяет регистрировать изменения объектов. Регистрация изменений происходит в той же транзакции, что и запись объекта. Если транзакция 1С откатывается, запись в плане обмена тоже исчезает. Если транзакция завершается успешно, запись остается.

Алгоритм действий следующий:

  1. Создаем новый ПланОбмена (назовем его, например, СинхронизацияСВнешнейБазой).
  2. Включаем в состав плана нужные справочники.
  3. В модуле объекта или в подписке на событие ПриЗаписи выполняем регистрацию изменений:

ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, Ссылка);

Далее создаем регламентное задание, которое будет работать вне основной транзакции записи. Оно будет анализировать таблицу изменений плана обмена, считывать данные объектов и передавать их в MS SQL. Поскольку регламентное задание видит только зафиксированные (Commit) данные, вы гарантированно не отправите во внешнюю базу данные, запись которых в 1С сорвалась.

Решение 2: Использование регистра-очереди (Паттерн Transactional Outbox)

Если планы обмена кажутся избыточными, проанализируем вариант с использованием вспомогательного РегистраСведений. Этот метод также основан на транзакционной целостности.

Рассмотрим пример кода в обработчике ПриЗаписи:


Процедура ПриЗаписи(Отказ)
    Если Отказ Тогда
        Возврат;
    КонецЕсли;

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

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

Решение 3: Событие ПослеЗаписи в формах

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

Посмотрим на пример реализации в форме:


&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    // Этот код выполнится только если объект реально записан в БД
    Сообщить("Объект успешно зафиксирован в базе данных.");
    ПередатьДанныеВВнешнююСистему(Объект.Ссылка);
КонецПроцедуры

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

Решение 4: Использование ДополнительныхСвойств и попытки записи

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


ОбъектСправочника = СсылкаНаЭлемент.ПолучитьОбъект();
ОбъектСправочника.Наименование = "Новое наименование";

Попытка
    ОбъектСправочника.Записать();
    // Если мы дошли до сюда, транзакция успешно зафиксирована (для простых случаев)
    ИнтеграцияСSQL.ОтправитьДанные(ОбъектСправочника.Ссылка);
Исключение
    // Сюда попадем, если был Отказ = Истина или системная ошибка
    ЗаписьЖурналаРегистрации("Ошибка записи", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки

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

Решение 5: Техническая проверка версии данных

Рассмотрим продвинутый способ проверки факта изменения объекта через анализ свойства ВерсияДанных. Платформа 1С меняет версию данных (строковое представление или метку времени) при каждой успешной записи в базу.

Разберем алгоритм проверки:

  1. Перед началом записи считываем текущую ВерсияДанных из базы.
  2. Выполняем Записать().
  3. Снова считываем версию данных из базы прямым запросом или через Ссылка.ВерсияДанных.
  4. Если версии различаются — запись прошла успешно.

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

Подведение итогов и лучшие практики

Проанализировав все варианты, можно сделать следующие выводы:

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

Выбор конкретного метода зависит от требований к скорости синхронизации и критичности данных, но в 90% случаев интеграции с внешними SQL-базами использование планов обмена является "золотым стандартом" разработки на платформе 1С.

← На главную