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