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