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