В практике разработки на платформе 1С:Предприятие 8 часто встречается ситуация, когда программное изменение реквизитов документа в процедуре ПередЗаписью ведет себя странно: на форме документа изменения видны сразу, а в списке документов или при проверке программным кодом данные обновляются только после повторного нажатия кнопки «Записать». В этой статье мы подробно разберем, почему так происходит, изучим механизмы работы транзакций и рассмотрим несколько способов решения этой задачи.
Рассмотрим типичный сценарий. У нас есть документ (например, «Реализация товаров и услуг»), в котором имеется реквизит ВестиВетУчет (Булево). Логика проста: если в табличной части «Товары» есть хотя бы одна номенклатура, у которой в карточке стоит флажок ам_ВестиВетеринарныйУчет, то и в самом документе флаг должен установиться автоматически.
Разработчик пишет следующий код в модуле объекта:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МАКСИМУМ(РеализацияТоваровУслугТовары.Номенклатура.ам_ВестиВетеринарныйУчет) КАК ВестиВетУчет
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
|ГДЕ
| РеализацияТоваровУслугТовары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
// ... выполнение запроса и присвоение значения реквизиту
КонецПроцедуры
Выясним причину ошибки: в момент выполнения события ПередЗаписью данные в базе данных (в таблицах SQL) еще не обновлены. Когда мы делаем запрос к таблице Документ.РеализацияТоваровУслуг.Товары, СУБД возвращает нам те данные, которые были сохранены в прошлый раз. Если это новый документ — запрос не вернет ничего. Если мы добавили новую строку в существующий документ — запрос «увидит» только старый состав строк.
Именно поэтому возникает эффект «второго раза»: при первой записи запрос берет старые данные, а при второй записи он уже видит данные, которые были записаны при первом нажатии.
Чтобы получить актуальные данные, которые пользователь только что ввел на форме (или которые были изменены программно в объекте), нам не нужно обращаться к базе данных через запрос к таблице документа. Эти данные уже находятся у нас в оперативной памяти — в коллекции ЭтотОбъект.Товары.
Рассмотрим правильный вариант запроса. Мы можем получить массив всей номенклатуры из табличной части и обратиться запросом не к документу, а к справочнику Номенклатура.
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Получаем массив уникальных ссылок номенклатуры из объекта в памяти
МассивНоменклатуры = ЭтотОбъект.Товары.ВыгрузитьКолонку("Номенклатура");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИСТИНА КАК ЕстьВетеринарныйТовар
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В(&СписокНоменклатуры)
| И Номенклатура.ам_ВестиВетеринарныйУчет = ИСТИНА";
Запрос.УстановитьПараметр("СписокНоменклатуры", МассивНоменклатуры);
РезультатЗапроса = Запрос.Выполнить();
// Если запрос не пустой, значит нашли хотя бы одну позицию с вет. учетом
ВестиВетУчет = НЕ РезультатЗапроса.Пустой();
КонецПроцедуры
В данном случае мы гарантированно работаем с актуальным составом табличной части, так как передаем список ссылок непосредственно из объекта, который готовится к записи.
Многие разработчики задаются вопросом: что быстрее — запрос к справочнику или простой перебор строк? Если в вашем документе обычно немного строк (до нескольких сотен), перебор коллекции может быть даже эффективнее, так как не требует обращения к СУБД для построения дерева запроса и передачи параметров.
Рассмотрим пример реализации через цикл:
ВестиВетУчет = Ложь;
Для Каждого СтрокаТЧ Из Товары Цикл
// Проверяем реквизит через точку.
// Внимание: это вызывает обращение к БД, если номенклатура не в кэше.
Если СтрокаТЧ.Номенклатура.ам_ВестиВетеринарныйУчет Тогда
ВестиВетУчет = Истина;
Прервать; // Достаточно найти одну строку
КонецЕсли;
КонецЦикла;
Важный нюанс: обращение через точку СтрокаТЧ.Номенклатура.ам_ВестиВетеринарныйУчет является "неявным" запросом. Если номенклатуры в документе много и она разная, это может создать нагрузку (N+1 запрос). В таких случаях вариант с Запросом (Решение №1) является более профессиональным и производительным.
Даже если мы исправили код в ПередЗаписью, иногда пользователи жалуются, что в динамическом списке флажок обновляется "с задержкой". Это связано с кэшированием данных на стороне клиента.
Для гарантированного обновления формы списка после записи объекта рекомендуется в модуле формы документа в процедуре ПослеЗаписи использовать метод оповещения:
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
// Оповещаем систему, что данный объект изменился.
// Это заставит динамические списки обновить текущую строку.
ОповеститьОбИзменении(Объект.Ссылка);
КонецПроцедуры
Для того чтобы данные записывались с первого раза, запомним основные правила:
ПередЗаписью к таблицам самого записываемого документа через запрос. В этот момент в базе данных лежат еще старые (неактуальные) значения.Товары, Услуги и т.д.Соблюдение этих архитектурных принципов 1С позволит избежать досадных ошибок с "повторной записью" и обеспечит высокую скорость работы вашей конфигурации.