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