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

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

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

В чем заключается разница между объектом и ссылкой

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

  1. Экземпляр объекта (НовОб.Реквизит) — это рабочая копия данных, находящаяся непосредственно в оперативной памяти текущего сеанса. Любые присвоения в модуле меняют значения именно в этой переменной.
  2. Обращение через ссылку (НовОб.Ссылка.Реквизит) — это не просто чтение памяти, а неявное считывание данных из базы данных (или кэша ссылок платформы). Ссылка возвращает только то состояние данных, которое было физически записано в таблицы СУБД.

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

Анализ типовой ошибки: заполнение в событии «ПриЗаписи»

Посмотрим на исходный пример кода, приводивший к ошибке при создании элемента:


НовОб = Справочники.усЕдиницыХранения.СоздатьЭлемент();
НовОб.Наименование = "ТестТест";
НовОб.Владелец = Номенклатура;
НовОб.Записать();

ЕдХран = НовОб.Ссылка;

При этом в модуле объекта разработчик разместил логику автозаполнения реквизита в событии ПриЗаписи:


Процедура ПриЗаписи(Отказ)
    Если НЕ ЗначениеЗаполнено(ПравилоРасчетаКлассаАВС) Тогда
        ПравилоРасчетаКлассаАВС = усОбщегоНазначенияСервер.ПолучитьЗначениеКонстанты("ПравилоРасчетаКлассаАВС_тг");
    КонецЕсли;
КонецПроцедуры

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

Более того, попытка вызвать повторно ЭтотОбъект.Записать() внутри процедуры ПриЗаписи вызовет фатальную ошибку рекурсивной записи объекта в транзакции. А если объект записывается пользователем из интерактивной формы, модификация данных в ПриЗаписи выставит флаг Модифицированность = Истина, из-за чего форма после сохранения будет требовать повторного закрытия или сохранения изменений.

Решение 1. Перенос логики в событие «ПередЗаписью»

Основным и наиболее простым решением является перенос алгоритмов валидации и автоматического заполнения полей в событие ПередЗаписью модуля объекта. Этот обработчик выполняется непосредственно перед сериализацией данных и отправкой их в СУБД в рамках общей транзакции.

Скорректируем код в модуле объекта:


Процедура ПередЗаписью(Отказ)
    // Проверяем и заполняем значение до момента отправки данных в СУБД
    Если НЕ ЗначениеЗаполнено(ПравилоРасчетаКлассаАВС) Тогда
        ПравилоРасчетаКлассаАВС = усОбщегоНазначенияСервер.ПолучитьЗначениеКонстанты("ПравилоРасчетаКлассаАВС_тг");
    КонецЕсли;
КонецПроцедуры

Теперь при вызове метода НовОб.Записать() платформа сначала выполнит код из ПередЗаписью, присвоит значение переменной ПравилоРасчетаКлассаАВС и только после этого выполнит запись всех реквизитов в таблицы базы данных. В результате и НовОб.ПравилоРасчетаКлассаАВС, и НовОб.Ссылка.ПравилоРасчетаКлассаАВС вернут одинаковое актуальное значение.

Решение 2. Использование подписки на событие «ПередЗаписью»

Если конфигурация находится на поддержке вендора и редактирование модуля объекта нежелательно, вынесем эту функциональность в отдельную подписку на события:

  1. В дереве метаданных создадим новую подписку на событие.
  2. В качестве объекта-источника выберем СправочникОбъект.усЕдиницыХранения.
  3. В качестве события укажем ПередЗаписью.
  4. В назначенном общем серверном модуле реализуем логику:

Процедура ПриЗаписиЕдиницыХраненияПередЗаписью(Источник, Отказ) Экспорт
    Если НЕ ЗначениеЗаполнено(Источник.ПравилоРасчетаКлассаАВС) Тогда
        Источник.ПравилоРасчетаКлассаАВС = усОбщегоНазначенияСервер.ПолучитьЗначениеКонстанты("ПравилоРасчетаКлассаАВС_тг");
    КонецЕсли;
КонецПроцедуры

Решение 3. Использование процедуры «ОбработкаЗаполнения»

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

В модуле объекта определяем стандартную процедуру:


Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
    // Заполняем реквизит значением по умолчанию для нового объекта
    Если НЕ ЗначениеЗаполнено(ПравилоРасчетаКлассаАВС) Тогда
        ПравилоРасчетаКлассаАВС = усОбщегоНазначенияСервер.ПолучитьЗначениеКонстанты("ПравилоРасчетаКлассаАВС_тг");
    КонецЕсли;
КонецПроцедуры

Чтобы данная процедура отработала при программном создании нового элемента в коде обработки, вызовем метод Заполнить():


НовОб = Справочники.усЕдиницыХранения.СоздатьЭлемент();
НовОб.Заполнить(Неопределено); // Инициирует вызов "ОбработкаЗаполнения"
НовОб.Наименование = "ТестТест";
НовОб.Владелец = Номенклатура;
НовОб.Записать();

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

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

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

← На главную