При разработке в среде 1С:Предприятие часто возникает задача программного добавления строк или изменения реквизитов табличной части документа. Разработчики нередко сталкиваются с ситуацией, когда попытка вынести логику в модуль менеджера или вызвать метод объекта приводит к ошибке «Метод объекта не обнаружен», а при сохранении данных в многопользовательской среде возникает исключение «Данные были изменены или удалены другим пользователем». Разберем по шагам, как устроена объектная модель платформы, почему возникают эти ошибки и как правильно модифицировать данные документа.
Выясним причину, по которой возникает ошибка «Метод объекта не обнаружен». В платформе 1С строго разделены контексты работы со ссылками, менеджерами и экземплярами объектов:
Документы.ИмяДокумента.ИмяМетода(). Этот модуль не содержит свойств конкретного документа и не имеет прямого доступа к его табличным частям.ПередЗаписью, ОбработкаПроведения, доступ к контексту ЭтотОбъект). Экспортные методы модуля объекта доступны только у полученного экземпляра типа ДокументОбъект.Если мы попытаемся вызвать экспортную функцию модуля объекта через менеджер документа, платформа выбросит исключение, так как менеджер не знает о методах конкретного экземпляра. Посмотрим на ошибочный и правильный варианты вызова.
Ошибочный вызов:
// Ошибка: "Метод объекта не обнаружен", так как метод объявлен в модуле объекта,
// а вызывается через менеджер документов
Документы.РеализацияТоваровУслуг.ОбновитьТабличнуюЧасть(СсылкаНаДокумент);
Корректный вызов метода объекта:
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// Вызываем экспортную процедуру модуля объекта у конкретного экземпляра
ДокументОбъект.ОбновитьТабличнуюЧасть();
ДокументОбъект.Записать();
Проанализируем ограничения платформы. В 1С:Предприятии не существует способа добавить или изменить строки в табличной части существующего документа без получения объекта (Ссылка.ПолучитьОбъект()) и его последующей записи. Табличные части являются неотъемлемой частью структуры документа и не могут модифицироваться точечно отдельными запросами в базу данных на уровне прикладного кода.
В модуле менеджера невозможно напрямую изменить реквизиты или строки табличной части документа. Для внесения изменений всегда требуется получить объект, модифицировать коллекцию строк и вызвать метод Записать().
Рассмотрим пример правильного добавления строки в табличную часть из внешнего кода или модуля менеджера:
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
НоваяСтрока = ДокументОбъект.ДополнительныеРеквизиты.Добавить();
НоваяСтрока.Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Согласовано");
НоваяСтрока.Значение = Истина;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Когда мы получаем объект методом Ссылка.ПолучитьОбъект(), платформа считывает текущую версию данных из информационной базы в оперативную память. Ошибка возникает из-за механизма оптимистической блокировки в следующих ситуациях:
Записать() платформа отклоняет сохранение устаревшей версии.Рассмотрим основные подходы для предотвращения и обработки ошибок параллельной записи.
1. Использование механизма повторных попыток (Retry)
В сценариях фоновой обработки или интеграции, где возможна конкуренция за документ, операцию обновления оборачивают в цикл с перехватом исключения и повторным считыванием актуальной версии объекта:
КоличествоПопыток = 3;
УспешноЗаписано = Ложь;
Для Счетчик = 1 По КоличествоПопыток Цикл
Попытка
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
НоваяСтрока = ДокументОбъект.ДополнительныеРеквизиты.Добавить();
НоваяСтрока.Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Статус");
НоваяСтрока.Значение = "Обработан";
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
УспешноЗаписано = Истина;
Прервать;
Исключение
Если Счетчик = КоличествоПопыток Тогда
ВызватьИсключение "Не удалось записать документ из-за конфликта блокировок: " + ОписаниеОшибки();
КонецЕсли;
КонецПопытки;
КонецЦикла;
2. Управляемые блокировки в транзакции
Если обновление документа происходит внутри сложной бизнес-транзакции, перед чтением объекта необходимо наложить исключительную управляемую блокировку, чтобы другие сеансы не могли изменить объект до завершения транзакции:
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
ЭлементБлокировки.УстановитьЗначение("Ссылка", СсылкаНаДокумент);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// Изменение табличной части и запись
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
3. Архитектурная альтернатива: использование регистров сведений
Если перед нами стоит задача частого обновления дополнительных параметров или статусов документа независимыми процессами, хранить эти данные в табличной части документа не рекомендуется. Каждая запись документа полностью блокирует его, требует пересчета итогов и может приводить к конфликтам блокировок. В таких случаях данные выносят в независимый регистр сведений, где измерением выступает ссылка на документ. Запись в регистр выполняется быстро и не требует перезаписи самого документа:
МенеджерЗаписи = РегистрыСведений.ДополнительныеСведения.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = СсылкаНаДокумент;
МенеджерЗаписи.Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("СтатусОбработки");
МенеджерЗаписи.Значение = "Выполнено";
МенеджерЗаписи.Записать(Истина);