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