Почему после записи документа в управляемой форме ввод на основании получает старые данные и как это исправить?

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

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

В чем архитектурная причина «запаздывания» данных?

Главная причина, по которой после вызова метода Записать() в рамках одной и той же клиентской процедуры данные в базе или в ссылке кажутся «старыми», кроется в механизме работы объектного кэша сеанса платформы 1С и особенностях обработки событий.

Рассмотрим подробно, что происходит на уровне платформы при выполнении проблемного кода:

  1. Модифицированные данные формы отправляются на сервер.
  2. Платформа выполняет запись объекта в СУБД и фиксирует транзакцию.
  3. Управление возвращается в ту же самую клиентскую процедуру. Однако локальный кэш представлений ссылок и кэш данных «через точку» в текущем сеансе клиента еще не знают об изменениях. Сброс и инвалидация локальных кэшей происходят только тогда, когда управление полностью возвращается в главный цикл событий платформы (event loop) на клиенте.
  4. Если сразу после вызова Записать() в той же процедуре открыть форму нового документа, платформа считывает данные старой версии объекта из своего оперативного кэша сеанса, игнорируя только что записанные в базу физические изменения.

Также в типовых конфигурациях (таких как ERP, Комплексная автоматизация или Управление торговлей) на запись документа могут быть назначены тяжелые фоновые задания (например, расчеты партионного учета или распределения запасов). Программная запись возвращает управление клиенту сразу после завершения основной транзакции документа, но фоновые процессы еще не успели обновить зависимые регистры, из-за чего ввод на основании считывает неактуальную информацию.

Попытки прочитать объект на сервере через конструкцию ПолучитьОбъект().Прочитать() часто оказываются неэффективными, так как метод ПолучитьОбъект() также обращается к кэшированным данным сеанса, а не напрямую к базе данных SQL.


Способ 1. Использование обработчика ожидания (Рекомендуемый)

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

Проанализируем пример реализации этого подхода. Перепишем клиентскую процедуру записи и вызовем процедуру открытия новой формы через 0.1 секунды:


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

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

Благодаря задержке в 0.1 секунды управление на мгновение возвращается в главный цикл событий платформы, кэш очищается, и к моменту вызова процедуры ОткрытьФормуЗаказаПоставщику система обращается к базе данных за самыми свежими значениями.


Способ 2. Принудительный сброс кэша через ОповеститьОбИзменении

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

Для этого сразу после записи документа вызовем метод глобального контекста ОповеститьОбИзменении(). Рассмотрим пример кода:


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

Этот метод заставляет клиентское приложение очистить кэш представлений ссылки и считать актуальные данные непосредственно из СУБД при инициализации формы нового документа.


Способ 3. Прямой запрос к базе данных на сервере

Если заполнение нового документа происходит в модуле объекта (например, в процедуре ОбработкаЗаполнения) и вам необходимо прочитать свежие данные документа-основания, забудьте об обращениях «через точку» вида ДанныеЗаполнения.СсылкаНаЗаказ.ИмяРеквизита и методе ПолучитьОбъект().

Единственный надежный способ получить 100% актуальные данные из СУБД в обход любых кэшей сеанса на сервере — это использование объекта Запрос. Разберем шаблон правильного чтения данных на сервере:


&НаСервере
Функция ПолучитьАктуальныеДанныеОснования(СсылкаНаДокумент)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ЗаказКлиента.Ссылка КАК Ссылка,
        |   ЗаказКлиента.Контрагент КАК Контрагент,
        |   ЗаказКлиента.СуммаДокумента КАК СуммаДокумента
        |ИЗ
        |   Документ.ЗаказКлиента КАК ЗаказКлиента
        |ГДЕ
        |   ЗаказКлиента.Ссылка = &Ссылка";
        
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        // Возвращаем структуру с гарантированно свежими данными
        Возврат Новый Структура("Контрагент, Сумма", Выборка.Контрагент, Выборка.СуммаДокумента);
    КонецЕсли;
    
    Возврат Неопределено;
    
КонецФункции

Использование прямого запроса полностью исключает влияние объектного кэша и гарантирует получение актуальных данных из таблиц СУБД сразу после успешной фиксации транзакции записи.

← На главную