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