Почему не сохраняются изменения реквизитов в подписке на событие "ПриЗаписи" регистра сведений?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Каждый разработчик на платформе «1С:Предприятие» рано или поздно сталкивается с ситуацией, когда код в обработчике события выполняется без каких-либо ошибок, но результат его работы не сохраняется в базе данных. Типичный пример — попытка программно заполнить или скорректировать реквизиты объекта (например, сгенерировать уникальный идентификатор GUID для записи регистра сведений) в подписке на событие ПриЗаписи. Код успешно проходит отладку, строка с новым идентификатором присваивается свойству записи, однако после завершения транзакции поле в базе данных остается пустым.

В этой статье мы подробно разберем, почему так происходит, изучим внутренние механизмы транзакций платформы 1С, исправим ошибку в коде и рассмотрим важные нюансы работы с событиями регистров сведений, о которых часто забывают даже опытные специалисты. Для наглядного анализа порядка вызова процедур платформы можно использовать инструмент для изучения последовательности событий 1С.

Выясняем причину: разница между "ПередЗаписью" и "ПриЗаписи"

Для того чтобы понять, почему изменения не записываются, проанализируем жизненный цикл записи объекта в базу данных (будь то документ, справочник или набор записей регистра). Когда платформа выполняет запись, этот процесс строго регламентирован и состоит из нескольких последовательных шагов на уровне СУБД:

  1. Начало транзакции. Все последующие действия выполняются в рамках единой неделимой транзакции базы данных. Если на любом из этапов произойдет ошибка, вся транзакция будет отменена (произойдет ROLLBACK).
  2. Вызов обработчика "ПередЗаписью" (Before Write). На этом этапе данные находятся исключительно в оперативной памяти сервера приложений 1С в виде объекта. Физического обращения к таблицам базы данных для вставки или обновления строк еще не произошло. Любые изменения реквизитов, которые мы внесем в объект-источник на данном этапе, будут учтены платформой при формировании итогового SQL-запроса.
  3. Запись в СУБД. Платформа транслирует состояние объекта из оперативной памяти в SQL-запрос (операции INSERT, UPDATE или DELETE) и отправляет его в СУБД. Данные физически переносятся в таблицы базы данных, но транзакция все еще остается открытой.
  4. Вызов обработчика "ПриЗаписи" (On Write). Этот обработчик срабатывает тогда, когда данные уже записаны в СУБД. С этого момента объект в памяти сервера 1С теряет непосредственную связь с транзакцией записи в плане модификации собственных полей. Если мы изменим значение реквизита (например, запишем свежесгенерированный GUID в строку набора записей регистра) в процедуре ПриЗаписи, это изменение затронет только копию данных в оперативной памяти. Платформа 1С не станет повторно генерировать SQL-запрос для обновления только что записанной строки в базе данных.
  5. Фиксация транзакции (COMMIT). Если ни на одном из шагов не возникло ошибок и параметр Отказ не был установлен в значение Истина, транзакция успешно завершается, и изменения становятся доступны другим пользователям системы.

Важное правило: Попытка вызвать метод Записать() внутри обработчика события ПриЗаписи для того же самого объекта приведет к бесконечному зацикливанию (рекурсии), аварийному переполнению стека и принудительному откату всей транзакции. Именно поэтому любые модификации полей самого записываемого объекта должны выполняться строго в событии ПередЗаписью.

Решение проблемы: переносим логику в "ПередЗаписью"

Давайте исправим логику работы нашей подписки. Для этого нам необходимо перенаправить подписку на событие ПередЗаписью соответствующего объекта. В случае с регистром сведений источником будет выступать объект типа РегистрСведенийНаборЗаписей.

Рассмотрим правильную реализацию обработчика подписки на событие:


Процедура ПрисвоитьРеквизитGUIDПередЗаписью(Источник, Отказ, Замещение) Экспорт

    // Перед обходом записей обязательно нужно учесть специфику регистров сведений,
    // о которой мы подробно поговорим ниже.
    Если Источник.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;

    Для Каждого Запись Из Источник Цикл
        Если Не ЗначениеЗаполнено(Запись.GUID) Тогда            
            Запись.GUID = Строка(Новый УникальныйИдентификатор);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

При таком подходе изменения будут внесены в коллекцию записей еще до того, как платформа сформирует SQL-пакет для отправки в базу данных, и наш уникальный идентификатор успешно сохранится в СУБД.

Специфика работы с регистрами сведений: эффект двойного вызова

При написании обработчиков для подписок на события регистров сведений крайне важно понимать одно неочевидное поведение платформы, которое называют "эффектом менеджера записи".

Если пользователь вносит изменения в периодический или непериодический регистр сведений интерактивно через форму записи или разработчик программно использует объект типа РегистрСведенийМенеджерЗаписи, платформа "1С:Предприятие" осуществляет запись в два этапа. Соответственно, обработчики событий ПередЗаписью и ПриЗаписи модуля набора записей сработают дважды за одну транзакцию:

Именно поэтому в начале нашей процедуры мы добавили обязательное условие:


Если Источник.Количество() = 0 Тогда
    Возврат;
EndIf;

Без этой проверки логика генерации GUID отработала бы вхолостую на первом шаге очистки данных, что увеличивает нагрузку на сервер и усложняет чтение кода при отладке.

Для чего же тогда нужен обработчик "ПриЗаписи"?

Раз мы выяснили, что менять сам записываемый объект в событии ПриЗаписи нельзя, логично задаться вопросом: для каких целей разработчики используют это событие?

Посмотрим на основные сценарии применения обработчика ПриЗаписи:

Сравнение "Было" и "Стало" в регистрах сведений

Еще одна классическая трудность, с которой сталкиваются программисты при работе с подписками на события регистров сведений, заключается в аудите изменений. В отличие от справочников или документов, у регистра сведений нет уникальной ссылки на объект в базе данных, по которой можно было бы прочитать его старое состояние до начала записи.

Как только управление передается в обработчик ПриЗаписи, старые данные в СУБД уже затерты новыми. Если вам требуется сравнить старые и новые значения ресурсов или реквизитов (например, для ведения истории изменений или логирования действий пользователя), стандартный запрос к таблице регистра вернет только новые данные.

Проанализируем, как профессиональные разработчики решают эту задачу на практике с помощью временного кэширования:

  1. В первом вызове события ПередЗаписью (когда набор записей еще пуст, но отборы уже установлены и транзакция началась) мы выполняем запрос к базе данных для чтения текущих ("старых") значений по полям отбора.
  2. Полученные "старые" данные мы временно сохраняем в кэш. В качестве такого хранилища в механизмах БСП (Библиотеки Стандартных Подсистем) используется специальный буфер обмена в параметрах сеанса или временное хранилище.
  3. Во втором вызове ПередЗаписью или уже непосредственно в ПриЗаписи мы извлекаем сохраненные ранее данные из кэша и сопоставляем их с новым набором записей. На основе этого сравнения мы можем точно определить, какие ресурсы изменились, и выполнить необходимые сопутствующие действия.

Подведем итоги

Для успешной и производительной работы с подписками на события в 1С всегда следуйте простым архитектурным правилам:

← На главную