При разработке на управляемых формах в системе «1С:Предприятие 8.3» разработчики часто сталкиваются с неприятной особенностью поведения интерфейса. Представим стандартную бизнес-задачу: в табличной части документа (например, «Заказ покупателя») отображается номенклатура, её единица измерения и соответствующий коэффициент. Чтобы не перегружать базу данных лишними реквизитами, разработчик решает вывести коэффициент «через точку» от единицы измерения. То есть в качестве пути к данным для колонки таблицы указывается ЕдиницаИзмерения.Коэффициент. На первый взгляд это выглядит изящным решением: данные автоматически подтягиваются на форму без написания лишнего кода.
Однако в процессе эксплуатации выясняется проблема. Пользователь открывает заказ, видит коэффициент «1». Затем, не закрывая форму заказа (или даже закрыв её и открыв снова через секунду), переходит в карточку единицы измерения, меняет коэффициент на «2», записывает элемент справочника и возвращается в заказ. К удивлению пользователя, в таблице по-прежнему отображается старое значение «1». Кажется, что платформа намертво кэширует данные. В этой статье мы подробно разберем, почему возникает этот эффект, проанализируем внутренние механизмы платформы «1С:Предприятие» и рассмотрим несколько способов решения этой проблемы — от быстрых обходных путей до правильного архитектурного проектирования.
Для того чтобы понять, как бороться с этим поведением, проанализируем ситуацию с технической стороны. Платформа «1С:Предприятие» спроектирована так, чтобы минимизировать количество обращений клиентского приложения к серверу базы данных. Передача данных по сети — это одна из самых ресурсоемких операций, поэтому система активно использует локальное кэширование на стороне клиента.
Когда управляемая форма впервые считывает и отображает реквизит «через точку» (например, ЕдиницаИзмерения.Коэффициент), происходит следующее:
Этот кэш имеет ограниченное время жизни (в среднем около 20 минут) или очищается при определенных действиях пользователя (например, при перезапуске клиентского сеанса). Именно поэтому «через некоторое время» или после перезахода в программу данные обновляются сами собой. Однако для пользователя, которому актуальные данные нужны прямо сейчас, такое поведение выглядит как досадная ошибка системы.
Часто начинающие разработчики пытаются решить проблему «в лоб», используя стандартные методы обновления интерфейса. Рассмотрим, почему они оказываются неэффективными в данной ситуации:
ЭтаФорма.ОбновитьОтображениеДанных() — данный метод предназначен исключительно для визуальной перерисовки элементов управления на экране (например, для применения условного оформления или обновления доступности кнопок). Он заставляет форму перерисовать картинку, но обращается он за данными всё к тому же локальному кэшу клиента. Если в кэше лежит старое значение, на экране снова отобразится именно оно.Элементы.ИмяТаблицы.Обновить() — метод обновляет визуальное представление конкретного элемента формы (таблицы), но логика работы с кэшем остается неизменной. Запрос к базе данных для получения новых значений реквизитов «через точку» не инициируется.ОповеститьОбИзменении()Если вам необходимо быстро решить проблему без кардинального изменения структуры данных, наиболее правильным инструментом будет использование встроенного метода платформы ОповеститьОбИзменении(). Этот метод сообщает платформе, что определенный объект базы данных (или группа объектов) был изменен, и заставляет систему принудительно очистить локальный кэш, связанный с этим объектом.
Разберем по шагам, как это работает на практике. Когда вы программно или интерактивно изменяете элемент справочника ЕдиницыИзмерения, вам нужно вызвать метод ОповеститьОбИзменении() на клиенте, передав туда измененную ссылку или тип объекта.
Посмотрим на пример кода, который можно разместить, например, в форме выбора или в обработчике закрытия карточки единицы измерения:
// Пример 1: Сброс кэша для конкретной измененной ссылки
СсылкаНаЕдиницу = Элементы.ТабличнаяЧасть.ТекущиеДанные.ЕдиницаИзмерения;
Если ЗначениеЗаполнено(СсылкаНаЕдиницу) Тогда
ОповеститьОбИзменении(СсылкаНаЕдиницу);
КонецЕсли;
Этот вызов сообщает платформе, что кэшированные данные для конкретной единицы измерения больше не актуальны. При следующем обращении формы к реквизиту ЕдиницаИзмерения.Коэффициент система проигнорирует старое кэшированное значение и выполнит свежий запрос к базе данных.
Существует также альтернативный вариант — сброс кэша для всех объектов определенного типа. Для этого в метод передается описание типа:
// Пример 2: Полный сброс кэша для всего справочника единиц измерения
ОповеститьОбИзменении(Тип("СправочникСсылка.ЕдиницыИзмерения"));
Важное предупреждение о производительности: Использование сброса кэша по типу (Пример 2) следует применять с большой осторожностью. Если полностью очистить кэш для всего справочника, платформе придется заново считывать данные всех единиц измерения при прокрутке любых таблиц во всех открытых формах текущего сеанса. Это может вызвать кратковременные зависания интерфейса («фризы»). Поэтому всегда отдавайте предпочтение передаче конкретной измененной ссылки (Пример 1).
Также стоит помнить, что метод ОповеститьОбИзменении() работает строго в рамках текущего клиентского сеанса. Если пользователь «А» изменил коэффициент, а у пользователя «Б» в этот момент открыт заказ, локальный кэш на компьютере пользователя «Б» об этом изменении не узнает до тех пор, пока форма не будет перечитана или кэш не устареет естественным путем.
Давайте проанализируем ситуацию глубже. Использование пути к данным «через точку» на управляемых формах часто называют архитектурной ошибкой или «решением от лукавого». И для этого есть две крайне веские причины:
Для того чтобы полностью исключить проблемы с кэшированием, обеспечить максимальное быстродействие системы и сохранить историческую достоверность данных, настоятельно рекомендуется использовать классический паттерн разработки 1С.
Разберем по шагам реализацию этого паттерна:
Товары) добавьте собственный реквизит Коэффициент с типом Число. Теперь это полноценное поле базы данных, принадлежащее самому документу.ЕдиницаИзмерения создайте обработчик события ПриИзменении. В этом обработчике мы будем программно считывать коэффициент из справочника и записывать его непосредственно в строку нашей таблицы.Рассмотрим пример реализации кода для формы документа:
&НаКлиенте
Процедура ТоварыЕдиницаИзмеренияПриИзменении(Элемент)
СтрокаТаблицы = Элементы.Товары.ТекущиеДанные;
Если СтрокаТаблицы <> Неопределено Тогда
// Получаем коэффициент с сервера и записываем в физический реквизит строки
СтрокаТаблицы.Коэффициент = ПолучитьКоэффициентЕдиницы(СтрокаТаблицы.ЕдиницаИзмерения);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьКоэффициентЕдиницы(Знач СсылкаНаЕдиницу)
Если ЗначениеЗаполнено(СсылкаНаЕдиницу) Тогда
Возврат СсылкаНаЕдиницу.Коэффициент;
КонецЕсли;
Возврат 1;
КонецФункции
При таком подходе мы получаем целый ряд неоспоримых преимуществ:
Таким образом, отказ от динамического отображения реквизитов через точку в пользу физического хранения данных в табличных частях — это единственный надежный путь к созданию стабильных, быстрых и отказоустойчивых бизнес-приложений на платформе «1С:Предприятие 8.3».