Почему реквизит документа заполнен в объекте, но остается пустым в ссылке и базе данных после записи?

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

В практике разработки на платформе «1С:Предприятие 8.3» программисты часто сталкиваются с ситуацией, когда при программной записи объекта (например, документа или справочника) данные, казалось бы, успешно присваиваются реквизитам в коде, но после выполнения метода Записать() в базе данных остается пустота. Особенно загадочно это выглядит в отладчике: в переменной типа ДокументОбъект значение есть, а если раскрыть свойство ДокументОбъект.Ссылка, то в соответствующем поле ссылки значения нет. Разберем детально, почему так происходит и как правильно организовать запись данных.

Анализ механизма записи объекта и транзакционного цикла

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

  1. Обработчик ПередЗаписью: На этом этапе данные всё еще находятся исключительно в оперативной памяти (в объекте). Это «последний шанс» изменить состояние самого объекта перед тем, как данные уйдут в таблицу базы данных.
  2. Физическая запись в СУБД: Платформа берет текущее состояние объекта из памяти и формирует SQL-запросы (INSERT или UPDATE) к таблицам базы данных. После этого момента любые изменения свойств объекта в оперативной памяти уже не влияют на то, что было отправлено в базу.
  3. Обработчик ПриЗаписи: Этот обработчик вызывается уже после того, как физическая запись в таблицы базы данных состоялась, но транзакция еще не зафиксирована (не сделан Commit).

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

Разбор проблемы кэширования и обращения через Ссылку

Рассмотрим подробнее момент из отладки: почему в ДокументОбъект значение есть, а в ДокументОбъект.Ссылка.ИмяРеквизита — нет? Выясним причину этого поведения.

Когда мы обращаемся к реквизиту через точку от ссылки (Ссылка.Реквизит), платформа выполняет запрос к базе данных (или к объектному кэшу, который синхронизирован с БД). Поскольку физическая запись в таблицу произошла на шаге №2, а наше изменение в коде случилось на шаге №3 (уже после записи), база данных хранит «старое» или пустое значение. В то же время сам ДокументОбъект содержит измененное значение в памяти. Это классическое состояние рассинхронизации объекта и его представления в базе данных внутри транзакции.

Особенности работы в расширениях конфигурации

Ситуация усугубляется, если разработка ведется в расширении. Рассмотрим, как влияют аннотации на результат:

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

Пример правильной реализации в расширении:


&Перед("ПередЗаписью")
Процедура Расш_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    // Заполняем реквизит до того, как данные будут переданы в СУБД
    ЭтотОбъект.ВажныйРеквизит = "НовоеЗначение";
КонецПроцедуры

Изменение поведения платформы начиная с версии 8.3.13

Важно проанализировать замечание из сообщения №7 форума. Раньше многие разработчики использовали обработчик ОбработкаПроведения для изменения реквизитов документа (например, ставили отметку «Документ проведен успешно»). В старых версиях платформы это работало: при проведении объект повторно записывался вместе с движениями.

Однако в современных версиях (8.3.13 и выше) платформа оптимизирует работу. Теперь изменения, внесенные в объект внутри процедуры ОбработкаПроведения, не записываются в базу данных. Это сделано для исключения неявных модификаций и ускорения работы системы. Рассмотрим, как теперь нужно действовать:

  1. Если нужно изменить реквизит в момент проведения, делайте это в ПередЗаписью, анализируя параметр РежимЗаписи.
  2. Проверяйте значение РежимЗаписиДокумента.Проведение.

Почему при перепроведении реквизиты заполнялись?

Автор заметил: «Когда перепроводишь документ, реквизиты заполняются». Выясним, почему так происходило. Часто программный код в ПриЗаписи пишется с использованием запросов к самой базе данных или к движениям документа. При первой записи (создании) движений еще нет, или объект еще не существует в базе в окончательном виде. При повторном проведении код в ПриЗаписи мог считывать уже имеющиеся в базе данные от предыдущей записи и обновлять объект в памяти. Это создавало иллюзию работы, но на самом деле являлось архитектурной ошибкой, так как данные в базе обновлялись только при следующем действии с документом.

Рекомендации при программной записи из обработок

Когда мы пишем внешнюю обработку для заполнения документов, нужно учитывать два критических момента:

1. Использование флага ОбменДанными.Загрузка

Если в вашем коде используется конструкция:


НовыйДокумент.ОбменДанными.Загрузка = Истина;
НовыйДокумент.Записать();

Помните, что в этом режиме большинство стандартных проверок и обработчиков (включая ПередЗаписью и ПриЗаписи) не выполняются (если в них не прописана специальная проверка Если ОбменДанными.Загрузка Тогда Возврат; КонецЕсли;). Если вы ждете, что реквизит заполнится сам внутри модуля объекта, этого не произойдет.

2. Использование ДополнительныеСвойства

Если вам нужно передать какую-то информацию из внешней обработки или из ПередЗаписью в ПриЗаписи (например, чтобы на основе нового реквизита создать запись в стороннем регистре), используйте структуру ДополнительныеСвойства. Рассмотрим пример:


// В обработчике ПередЗаписью
ЭтотОбъект.ДополнительныеСвойства.Вставить("НужноЛиЛогировать", Истина);

// В обработчике ПриЗаписи
Если ЭтотОбъект.ДополнительныеСвойства.Свойство("НужноЛиЛогировать") Тогда
    // Выполняем действия, внешние по отношению к объекту
    ЗаписатьВЛог(ЭтотОбъект.Ссылка);
КонецЕсли;

Итоговые выводы

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

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

← На главную