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