Как правильно записать неосновной реквизит объекта на управляемой форме и избежать ошибки несоответствия версии?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей IT и автоматизация бизнеса
← На главную

При разработке управляемых форм часто возникает необходимость редактировать на одной форме данные сразу двух связанных объектов. Например, основной реквизит формы представляет собой один справочник, а дополнительный реквизит формы содержит объект другого справочника. При попытке сохранить такой вспомогательный объект разработчики нередко сталкиваются с ошибкой: «Операция не может быть выполнена из-за несоответствия версии или отсутствия записи базы данных (возможно, запись была изменена или удалена)!».

Разберем причины возникновения этой ошибки, выясним, как работает механизм версионирования платформы 1С:Предприятие 8, и рассмотрим корректные подходы к сохранению дополнительных объектов на форме.

Почему возникает ошибка несоответствия версии данных?

Проанализируем механику работы управляемой формы. Когда на форму выносится реквизит ссылочного объектного типа (например, СправочникОбъект.ИнформационныеКарты), платформа помещает в контекст формы не только поля объекта, но и скрытое служебное поле ВерсияДанных.

При вызове метода РеквизитФормыВЗначение("КартаОбъект") платформа считывает данные из формы и выполняет проверку оптимистической блокировки: сверяет скрытую версию данных реквизита формы с версией записи в таблице базы данных. Когда объект записывается вызовом метода Записать(), в СУБД формируется новая версия записи. Если после этого не обновить сам реквизит формы, в контексте формы останется старая версия данных. Соответственно, при повторном обращении к РеквизитФормыВЗначение платформа видит расхождение версий и выбрасывает исключение.

Важно помнить, что управляемая форма берет на себя автоматический контроль жизненного цикла, валидацию и транзакционную запись исключительно для Основного реквизита. Управление всеми остальными реквизитами объектных типов возлагается на разработчика.

Способ 1. Синхронизация реквизита формы после записи объекта

Рассмотрим прямое решение проблемы в лоб, когда тип реквизита формы сохранен как СправочникОбъект. Чтобы платформа не выдавала ошибку при повторных записях, необходимо после вызова метода Записать() вернуть актуализированный объект обратно в реквизит формы с помощью метода ЗначениеВРеквизитФормы. Это обновит сохраненную в форме версию данных.

Посмотрим на пример серверной процедуры:


&НаСервере
Процедура СохранитьДополнительныйОбъектНаСервере()
    // 1. Преобразуем реквизит формы в прикладной объект
    ИК_Объект = РеквизитФормыВЗначение("КартаОбъект");
    
    // 2. Выполняем запись объекта в базу данных
    ИК_Объект.Записать();
    
    // 3. Возвращаем записанный объект с обновленной версией данных обратно в форму
    ЗначениеВРеквизитФормы(ИК_Объект, "КартаОбъект");
    
    // 4. При необходимости связываем ссылку с основным объектом
    ЭтаФорма.Объект.Владелец = ЭтаФорма.КартаОбъект.Ссылка;
КонецПроцедуры

Разберем по шагам, что происходит в этом коде:

  1. Метод РеквизитФормыВЗначение инициализирует объект СправочникОбъект значениями, заполненными пользователем на форме.
  2. Метод Записать() фиксирует изменения в СУБД и формирует новое значение версии данных.
  3. Метод ЗначениеВРеквизитФормы передает свежее состояние объекта (включая обновленную версию) обратно в форму, предотвращая рассинхронизацию.

Способ 2. Оптимальный архитектурный подход (DTO / простые реквизиты)

Хранение тяжелого типа СправочникОбъект в качестве реквизита управляемой формы считается неоптимальной практикой, так как это увеличивает объем контекста формы, передаваемого между клиентом и сервером. Более надежный и производительный подход — хранить на форме ссылку на вспомогательный объект и простые реквизиты для редактируемых полей.

Рассмотрим, как реализовать этот подход в типовых обработчиках формы:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    // Если объект новый или ссылка не заполнена, создаем новый объект
    Если Не ЗначениеЗаполнено(РеквизитФормыСсылкаНаКарту) Тогда
        ВспомогательныйОбъект = Справочники.ИнформационныеКарты.СоздатьЭлемент();
    Иначе
        ВспомогательныйОбъект = РеквизитФормыСсылкаНаКарту.ПолучитьОбъект();
    КонецЕсли;
    
    // Заполняем поля объекта из простых реквизитов формы
    ВспомогательныйОбъект.КодКарты = РеквизитФормыКодКарты;
    ВспомогательныйОбъект.Наименование = РеквизитФормыНаименованиеКарты;
    
    // Проверяем заполнение и записываем объект
    Если Не ВспомогательныйОбъект.ПроверитьЗаполнение() Тогда
        Отказ = Истина;
        Возврат;
    КонецЕсли;
    
    Попытка
        // Запись выполняется в рамках транзакции записи основного объекта
        ВспомогательныйОбъект.Записать();
        РеквизитФормыСсылкаНаКарту = ВспомогательныйОбъект.Ссылка;
        ТекущийОбъект.Владелец = ВспомогательныйОбъект.Ссылка;
    Исключение
        Отказ = Истина;
        ВызватьИсключение;
    КонецПопытки;
КонецПроцедуры

Преимущества данного подхода:

  1. Транзакционная целостность: запись вспомогательного объекта выполняется внутри события ПередЗаписьюНаСервере, где уже открыта системная транзакция записи основного объекта. Если в одном из объектов возникнет ошибка, откатятся изменения обоих справочников.
  2. Отсутствие проблем с версионированием: на форме нет типа СправочникОбъект, а значит, исключены конфликты оптимистической блокировки при повторных преобразованиях.
  3. Экономия трафика: между клиентом и сервером передаются только базовые типы данных.

Управление пессимистическими блокировками

Если в системе одновременно работают пользователи на управляемых и обычных формах, либо требуется гарантировать эксклюзивное редактирование вспомогательного объекта при открытии карточки, необходимо явно управлять блокировкой данных.

Для этого применяются системные методы ЗаблокироватьДанныеДляРедактирования и РазблокироватьДанныеДляРедактирования:


&НаСервере
Процедура ЗаблокироватьВспомогательныйОбъект()
    Если ЗначениеЗаполнено(ЭтаФорма.КартаОбъект.Ссылка) Тогда
        // Устанавливаем пессимистическую блокировку, передавая ссылку, версию и ключ формы
        ЗаблокироватьДанныеДляРедактирования(
            ЭтаФорма.КартаОбъект.Ссылка,
            ЭтаФорма.КартаОбъект.ВерсияДанных,
            ЭтаФорма.УникальныйИдентификатор
        );
    КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура РазблокироватьВспомогательныйОбъект()
    Если ЗначениеЗаполнено(ЭтаФорма.КартаОбъект.Ссылка) Тогда
        // Снимаем блокировку по ссылке и идентификатору формы
        РазблокироватьДанныеДляРедактирования(
            ЭтаФорма.КартаОбъект.Ссылка,
            ЭтаФорма.УникальныйИдентификатор
        );
    КонецЕсли;
КонецПроцедуры

Дополнительные важные нюансы при работе с неосновными объектами

  1. Модифицированность формы: Изменение пользователем полей, связанных с дополнительным реквизитом, не всегда переводит флаг Модифицированность формы в значение Истина. Рекомендуется в событиях элементов управления ПриИзменении явно указывать: ЭтаФорма.Модифицированность = Истина;.
  2. Оповещение интерфейса: Так как вспомогательный объект записывается программно, динамические списки в других открытых окнах не обновятся автоматически. После успешной записи на клиенте следует вызвать метод ОповеститьОбИзменении(СсылкаНаОбъект).
  3. Проверка заполнения: Стандартная валидация платформы не проверяет реквизиты неосновного объекта перед закрытием формы. Необходимо явно вызывать ПроверитьЗаполнение() в коде перед сохранением.
← На главную