Как при записи объекта в 1С предотвратить сохранение изменений без вызова ошибки?

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

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

Если мы попытаемся пойти стандартным путем и пропишем в обработчике ПередЗаписью конструкцию Отказ = Истина, это приведет к прерыванию транзакции. Внешний обмен получит системную ошибку, аварийно завершит свою работу и, скорее всего, заблокирует последующую загрузку остальных данных. Нам же необходимо «обмануть» систему: сделать так, чтобы платформа выполнила запись, не зафиксировав при этом никаких изменений, и рапортовала внешнему обмену об успешном завершении операции.

В этой статье мы подробно разберем элегантное техническое решение этой задачи, проанализируем скрытые риски его использования на уровне СУБД, рассмотрим влияние на планы обмена и изучим альтернативные архитектурные паттерны.

Суть приема: использование метода Прочитать()

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

Рассмотрим, как выглядит этот код на практике в модуле объекта (или в подписке на событие):


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

Давайте подробно разберем по шагам, что происходит во время выполнения этого кода:

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

Взаимодействие с СУБД и системным реквизитом ВерсияДанных

Чтобы понять всю глубину этого приема, выясним причину, почему обычное присвоение реквизитов из базы (например, через ручное чтение ссылки) может работать некорректно или вызывать конфликты блокировок.

У каждого ссылочного объекта в платформе «1С:Предприятие 8» присутствует скрытый системный реквизит ВерсияДанных. Он используется для реализации механизма оптимистической блокировки. Когда сторонний обмен присылает измененный объект, у него в оперативной памяти может содержаться устаревшая или вообще незаполненная версия данных.

Если мы попытаемся перезаписать такой объект стандартным способом, платформа выполнит сравнение версий. Не обнаружив соответствия между версией в памяти и версией в СУБД, система выбросит критическое исключение: «Операция не может быть выполнена из-за несоответствия версии...».

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

Скрытая опасность: зацикливание в планах обмена (рекурсивный обмен)

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

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

В результате возникает следующая проблема:

  1. Объект приходит из Базы А в Базу Б.
  2. База Б «сбрасывает» изменения через Прочитать() и успешно записывает старую версию.
  3. Запись инициирует регистрацию объекта в Базе Б для отправки обратно в Базу А.
  4. При следующем сеансе обмена База Б отправляет этот объект обратно. Это может вызвать бесконечный цикл гонки данных и лавинообразный рост таблиц регистрации изменений.

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


Процедура ПередЗаписью(Отказ)
	Если ОбменДанными.Загрузка Тогда
		Если Не РазрешеноИзменятьОбъект() Тогда
			ЭтотОбъект.Прочитать();
			// Очищаем регистрацию изменений для всех узлов планов обмена
			ЭтотОбъект.ОбменДанными.Получатели.Очистить();
		КонецЕсли;
	КонецЕсли;
КонецПроцедуры

Влияние на производительность и транзакционные блокировки

С точки зрения архитектуры высоконагруженных систем данный метод является «антипаттерном» (anti-pattern) и должен применяться только в крайних случаях, когда другие пути решения полностью заблокированы. Рассмотрим подробнее почему:

Правильные альтернативные решения

Если у вас есть возможность повлиять на архитектуру обмена данными, рекомендуется отказаться от использования метода Прочитать() в пользу более чистых решений:

  1. Фильтрация на стороне источника: Самый правильный способ — настроить правила регистрации изменений на стороне отправляющей базы так, чтобы запрещенные версии объектов или специфические объекты вообще не регистрировались к отправке и не попадали в файл выгрузки.
  2. Использование логики правил конвертации (КД): Если обмен построен на базе технологии «Конвертация данных», отказ от перезаписи логично перенести в обработчик события «Перед загрузкой объекта» или «При загрузке». Установка флага Отказ = Истина на уровне правил конвертации просто пропустит этот объект в XML/JSON-пакете, предотвращая даже попытку открытия транзакции записи в СУБД.
  3. Использование ДополнительныеСвойства: Передавать во входящем объекте специальный маркер в структуре ДополнительныеСвойства. В подписке проверять этот маркер и, если объект не должен меняться, управлять ходом записи на более ранних этапах.

Интересный нюанс: применение приема в регистрах сведений

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

Когда происходит удаление набора записей регистра сведений, в обработчик ПередЗаписью() передается абсолютно пустой набор (Количество() = 0). Из-за этого разработчик в коде не может понять, какие именно данные сейчас удаляются из системы (например, чтобы записать их в журнал регистрации или историю изменений).

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

← На главную