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