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