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