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