Как исправить ошибку «Нельзя изменять поле, содержащее объект данных формы» в 1С 8.3

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

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

В этой статье мы подробно разберем, почему возникает данная ошибка, изучим архитектурные особенности управляемого приложения и рассмотрим три надежных способа решения этой проблемы с примерами готового кода.

Выясняем причину: почему код из обычных форм не работает?

Давайте проанализируем ситуацию. В обычных формах мы могли напрямую связывать элементы интерфейса с тяжелыми прикладными объектами базы данных. Реквизит формы мог без проблем хранить в себе тип РегистрСведенийМенеджерЗаписи.

В управляемых формах архитектура построена по принципиально иной схеме. Чтобы минимизировать сетевой трафик между клиентом и сервером, платформа не передает тяжелые объекты базы данных на клиента. Вместо них используются облегченные типы-оболочки — так называемые данные формы. Наш реквизит формы с именем ЗаписьКИ на самом деле имеет тип ДанныеФормыСтруктура, а не прикладной тип РегистрСведенийМенеджерЗаписи.

Рассмотрим проблемный участок кода, который вызывает ошибку:


ЗаписьКИ_ = РегистрыСведений.КонтактнаяИнформация.СоздатьМенеджерЗаписи();
ЗаписьКИ_.Активность = Истина;
ЗаписьКИ_.Вид = Справочники.ВидыКонтактнойИнформации.НашАдрес;
ЗаписьКИ_.Тип = Перечисления.ТипыКонтактнойИнформации.Адрес;
ЗаписьКИ_.Объект = СсылкаНаОбъект;

Если Не ЭтоНовый() Тогда 
    ЗаписьКИ_.Прочитать();
КонецЕсли;    

// Здесь происходит сбой:
ЗаписьКИ = ЗаписьКИ_; 

Попытка выполнить прямое присваивание ЗаписьКИ = ЗаписьКИ_ трактуется платформой как попытка уничтожить системную структуру управляемой формы ДанныеФормыСтруктура и записать на её место физический объект базы данных РегистрСведенийМенеджерЗаписи. Платформа блокирует это действие в целях защиты целостности контекста формы и выдает ошибку.

Чтобы решить эту задачу, нам необходимо использовать механизмы взаимного преобразования типов, заложенные в платформу «1С:Предприятие».

Решение 1. Использование метода ЗначениеВРеквизитФормы (контекстный серверный вызов)

Если наш код выполняется на сервере в контексте формы (процедура или функция имеет директиву компиляции &НаСервере), самым правильным и простым решением будет использование встроенного метода формы ЗначениеВРеквизитФормы().

Давайте перепишем наш исходный код правильно:


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

КонецПроцедуры

Важная деталь: обратите внимание на параметры метода ЗначениеВРеквизитФормы(). Первым параметром мы передаем наш реальный прикладной объект базы данных (переменную ЗаписьКИ_), а вторым параметром — строковое имя реквизита управляемой формы (в кавычках "ЗаписьКИ").

Решение 2. Использование метода ЗначениеВДанныеФормы (внеконтекстный вызов)

Иногда логику работы с данными выносят в неконтекстные методы формы (с директивой &НаСервереБезКонтекста) или в общие серверные модули для оптимизации скорости работы системы. В таких случаях метод ЗначениеВРеквизитФормы() недоступен, так как у процедуры нет доступа к контексту конкретной формы.

В этой ситуации нам на помощь приходит глобальный метод платформы ЗначениеВДанныеФормы(). Давайте посмотрим, как будет выглядеть код в этом случае:


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

КонецПроцедуры

Важная деталь: при использовании метода ЗначениеВДанныеФормы() вторым параметром передается сама переменная реквизита формы (в нашем случае ЗаписьКИ), а не строка с ее именем!

Решение 3. Быстрое копирование через ЗаполнитьЗначенияСвойств

Если структура нашего реквизита формы ЗаписьКИ полностью совпадает с полями менеджера записи регистра сведений, мы можем обойтись без полной конвертации типов. Достаточно просто перенести значения всех полей из одного объекта в другой.

Для этого воспользуемся стандартной быстрой функцией платформы:


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

КонецПроцедуры

Этот способ работает очень быстро, поскольку он не пересоздает структуру данных на форме, а лишь заполняет имеющиеся поля значениями, прочитанными из базы данных.

Обратный процесс: как сохранить измененные данные формы в базу данных?

Разобравшись с заполнением формы, важно не забыть про обратную процедуру. Когда пользователь меняет адрес на форме, данные хранятся в типе ДанныеФормыСтруктура. У этого типа нет метода Записать(), поэтому мы не сможем сохранить изменения напрямую.

Для записи изменений нам нужно выполнить обратное преобразование — из реквизита формы получить прикладной объект базы данных. Разберем, как это сделать:


&НаСервере
Процедура СохранитьАдресНаСервере()
    
    // Получаем полноценный менеджер записи из реквизита формы
    МенеджерЗаписиБД = РеквизитФормыВЗначение("ЗаписьКИ");
    
    // Теперь нам доступен весь функционал прикладного объекта
    МенеджерЗаписиБД.Записать();
    
КонецПроцедуры

Метод РеквизитФормыВЗначение() принимает имя реквизита формы в виде строки, создает на сервере реальный объект РегистрСведенийМенеджерЗаписи, заполняет его данными из формы и возвращает нам. После этого мы можем вызвать стандартный метод Записать().

Резюме правил работы с контекстом в управляемых формах

Для закрепления материала систематизируем правила использования методов конвертации данных управляемой формы:

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

Используя предложенные инструкции и понимая разницу между объектами базы данных и структурами данных формы, вы легко сможете переносить любой старый код на управляемые формы 1С 8.3.

← На главную