Как передать данные между подписками на события ПередЗаписью и ПриЗаписи в 1С

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

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

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

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

Основное решение: использование свойства ДополнительныеСвойства

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

Рассмотрим, как передать признаки новизны и изменения реквизитов из первой подписки во вторую.

Шаг 1. Обработка в подписке ПередЗаписью

В подписке на событие ПередЗаписью проверяем, новый ли это объект, либо считываем старые данные из базы и сравниваем с текущими. Результаты анализа сохраняем в структуру ДополнительныеСвойства:


Процедура спр_ПередЗаписью(Источник, Отказ) Экспорт

	// Обязательно исключаем выполнение логики при обмене данными
	Если Источник.ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;

	ЭтоНовыйОбъект = Источник.ЭтоНовый();
	ДанныеИзменились = Ложь;

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

	// Передаем вычисленные флаги в структуру ДополнительныеСвойства
	Источник.ДополнительныеСвойства.Вставить("ЭтоНовыйОбъект", ЭтоНовыйОбъект);
	Источник.ДополнительныеСвойства.Вставить("ДанныеИзменились", ДанныеИзменились);

КонецПроцедуры

Шаг 2. Обработка в подписке ПриЗаписи

В обработчике ПриЗаписи извлекаем сохраненные значения и выполняем регистрацию записей в регистре сведений:


Процедура спр_ПриЗаписи(Источник, Отказ) Экспорт

	Если Источник.ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;

	ЭтоНовыйОбъект = Ложь;
	ДанныеИзменились = Ложь;

	// Безопасно считываем переданные свойства
	Если НЕ Источник.ДополнительныеСвойства.Свойство("ЭтоНовыйОбъект", ЭтоНовыйОбъект) Тогда
		Возврат;
	КонецЕсли;
	
	Источник.ДополнительныеСвойства.Свойство("ДанныеИзменились", ДанныеИзменились);

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

КонецПроцедуры

Альтернативное решение: получение ссылки нового объекта до записи

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

Разберем, как сгенерировать ссылку программно:

  1. Вызываем метод Источник.ПолучитьСсылкуНового().
  2. Если метод возвращает пустую ссылку или Неопределено, генерируем ссылку на основе нового уникального идентификатора через Справочники[ИмяСправочника].ПолучитьСсылку(Новый УникальныйИдентификатор()).
  3. Явно назначаем сгенерированную ссылку объекту с помощью метода Источник.УстановитьСсылкуНового(НоваяСсылка).

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


Процедура спр_ПередЗаписью_ОдноСобытие(Источник, Отказ) Экспорт

	Если Источник.ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;

	Если Источник.ЭтоНовый() Тогда
		СсылкаНаОбъект = Источник.ПолучитьСсылкуНового();
		Если НЕ ЗначениеЗаполнено(СсылкаНаОбъект) Тогда
			СсылкаНаОбъект = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор());
			Источник.УстановитьСсылкуНового(СсылкаНаОбъект);
		КонецЕсли;
		Действие = "Создан";
	Иначе
		СсылкаНаОбъект = Источник.Ссылка;
		Действие = "Изменен";
	КонецЕсли;

	// Формируем движение в регистре сведений
	МенеджерЗаписи = РегистрыСведений.ИсторияИзмененияОбъектов.СоздатьМенеджерЗаписи();
	МенеджерЗаписи.Период = ТекущаяДатаСеанса();
	МенеджерЗаписи.Объект = СсылкаНаОбъект;
	МенеджерЗаписи.Действие = Действие;
	МенеджерЗаписи.Записать();

КонецПроцедуры

Важные правила и технические особенности

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

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