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