Переход с платформы 1С:Предприятие 7.7 на 1С:Предприятие 8 часто сопровождается трудностями, связанными с изменением базовой архитектуры работы с базой данных. Типичный пример — задача каскадного перепроведения цепочки документов (например, основного договора и подчиненных ему дополнительных соглашений) при изменении любого из них. Разработчики, привыкшие к механизмам «семерки», часто пытаются решить эту задачу «в лоб» через транзакции записи, сталкиваясь с неприятной ошибкой: «Редактируемый объект изменен или удален другим пользователем (версия объекта изменилась)».
В этой статье мы подробно разберем, почему возникает эта ошибка оптимистической блокировки, как исправить ее в текущем коде с помощью механизма дополнительных свойств и какие современные архитектурные паттерны (Best Practices) следует использовать для решения подобных задач в платформе 1С 8.3.
Для начала выясним первопричину проблемы. В платформе 1С 8.х реализован механизм оптимистической блокировки объектов. Его суть заключается в следующем:
ПриЗаписи, ОбработкаПроведения или ПослеЗаписи) сторонняя процедура считывает этот же документ из базы через метод ПолучитьОбъект(), как-то модифицирует его и записывает, то версия объекта в базе данных меняется раньше, чем завершится основная транзакция исходного объекта.Когда мы пытаемся при проведении подчиненного документа запустить процедуру полного перепроведения всей цепочки (включая этот самый подчиненный документ), мы гарантированно попадаем в эту ловушку. Система пытается повторно записать объект, который прямо сейчас находится в процессе записи.
Рассмотрим детально три основных способа решения этой проблемы, начиная от локального исправления кода и заканчивая правильной архитектурной перестройкой логики приложения.
Если вам необходимо решить задачу быстро, без глобального изменения логики конфигурации, мы можем использовать специальное свойство любого объекта 1С — коллекцию ДополнительныеСвойства. Это структура, которая существует только во время жизненного цикла объекта в оперативной памяти и не записывается в базу данных. С ее помощью мы можем передавать «флаги» между различными обработчиками событий и избегать бесконечной рекурсии при каскадной записи связанных документов.
Разберем этот алгоритм по шагам:
ДополнительныеСвойства.Посмотрим на пример программной реализации этого подхода в обработчике события ПриЗаписи:
Процедура ПриЗаписи(Отказ)
// Проверяем, не запущено ли уже каскадное перепроведение этой группы
Если Проведен И Не ДополнительныеСвойства.Свойство("ПерезаписьГруппыДокументов") Тогда
// Создаем структуру параметров для передачи в подчиненные объекты
ПараметрыПерезаписи = Новый Структура("Источник", Ссылка);
// Получаем список всех связанных документов (основного и подчиненных)
СписокДокументов = ПолучитьСписокСвязанныхДокументов(Ссылка);
Для Каждого СтрокаДокумента Из СписокДокументов Цикл
// Если документ в списке — это текущий документ, пропускаем его,
// так как платформа сама запишет его в конце текущей транзакции
Если СтрокаДокумента.Ссылка = Ссылка Тогда
Продолжить;
КонецЕсли;
// Получаем объект связанного документа для записи
ДокументОбъект = СтрокаДокумента.Ссылка.ПолучитьОбъект();
// Внедряем флаг в дополнительные свойства связанного объекта
ДокументОбъект.ДополнительныеСвойства.Вставить("ПерезаписьГруппыДокументов", ПараметрыПерезаписи);
Попытка
// Проводим связанный документ программно
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Обязательно обрабатываем исключения, чтобы не заблокировать работу пользователя
ЗаписьЖурналаРегистрации("КаскадноеПроведение",
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Отказ = Истина;
Прервать;
КонецПопытки;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Важное примечание: Обратите внимание на условие исключения текущего документа из цикла перепроведения (Если СтрокаДокумента.Ссылка = Ссылка Тогда Продолжить;). Это ключевой момент, который убережет вас от ошибки изменения версии текущего объекта.
Рассмотрим более глубокий анализ бизнес-логики, представленной в обсуждении. Зачастую разработчики используют перепроведение документов только ради одной цели — перестроить записи в периодическом регистре сведений (например, графики аренды или действующие соглашения). При этом сами реквизиты документов никак не меняются.
В платформе 1С 8.3 проведение документов «в цепочке» ради актуализации регистра — это явный антипаттерн, приводящий к избыточной нагрузке на СУБД и конфликтам блокировок. Вместо этого мы можем использовать прямую запись движений по регистратору.
Выясним, как это работает:
РегистрСведенийНаборЗаписей с установленным отбором по конкретному документу.Записать(Истина).Проанализируем пример кода, реализующий этот подход без перепроведения самих документов:
Процедура АктуализироватьРегистрАрендыПоДоговору(СсылкаНаДоговор) Экспорт
// Шаг 1. Получаем таблицу актуальных периодов по всей цепочке договора
ТаблицаПериодов = РассчитатьПериодыАрендыПоДоговору(СсылкаНаДоговор);
// Шаг 2. Группируем данные по регистраторам, чтобы обновить движения точечно
Для Каждого СтрокаДоговора Из ТаблицаПериодов.СкопироватьКолонку("Регистратор") Цикл
// Создаем набор записей для конкретного регистратора (документа)
НаборЗаписей = РегистрыСведений.ТекущийДокументАренды.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СтрокаДоговора.Регистратор);
// Находим строки, относящиеся к этому регистратору
СтрокиДляЗаписи = ТаблицаПериодов.НайтиСтроки(Новый Структура("Регистратор", СтрокаДоговора.Регистратор));
НаборЗаписей.Очистить();
Для Каждого СтрокаДанных Из СтрокиДляЗаписи Цикл
НоваяЗапись = НаборЗаписей.Добавить();
ЗаполнитьЗначенияСвойств(НоваяЗапись, СтрокаДанных);
КонецЦикла;
// Записываем движения напрямую, не трогая сам документ
НаборЗаписей.Записать(Истина);
КонецЦикла;
КонецПроцедуры
При таком подходе пользователи вообще не заметят задержек, транзакция записи документа пройдет мгновенно, а данные в регистре сведений всегда будут в актуальном и непротиворечивом состоянии.
В высоконагруженных многопользовательских системах проведение связанных документов непосредственно в момент записи пользователем может полностью заблокировать работу всей базы данных. Если один документ блокирует таблицы при проведении, другие пользователи не смогут провести свои документы по этой же организации или договору.
Наиболее современным стандартом разработки в среде 1С (принятым в типовых решениях уровня ERP и УТ) является отложенное проведение через очередь. Схема выглядит следующим образом:
ОчередьПересчетаДоговоров с единственным измерением Договор типа ДокументСсылка (или СправочникСсылка).Этот подход гарантирует высокую скорость отклика системы для конечных пользователей и полностью исключает возникновение взаимных блокировок СУБД.
При проектировании механизмов каскадного проведения документов в 1С:Предприятии 8 всегда соотносите техническое решение с реальными потребностями бизнеса и архитектурными ограничениями платформы: