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