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