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