При разработке подписок на события и обработчиков записи справочников или документов часто возникает задача: выяснить, изменились ли ключевые реквизиты объекта (например, родительская группа или вид номенклатуры), и повлияет ли это на связанные данные — такие как счета учета номенклатуры в регистрах сведений. Разберем механику работы платформы 1С с объектной моделью и базой данных в событии ПередЗаписью, а также выясним, как правильно строить запросы для еще не сохраненных данных.
Проанализируем распространенное заблуждение относительно метода ПолучитьСсылкуНового() и понятия «новой ссылки». Метод ПолучитьСсылкуНового() предназначен исключительно для объектов, которые еще ни разу не были записаны в информационную базу (когда выражение ЭтотОбъект.ЭтоНовый() возвращает Истина).
Если объект уже существует в базе данных, его ссылка (уникальный идентификатор GUID) зафиксирована и остается неизменной независимо от того, сколько раз и какие реквизиты изменяются. Поэтому получить «ссылку на измененный объект» невозможно — ссылка у существующего объекта в памяти и в базе данных абсолютно одинакова.
Важно помнить разницу между объектом в оперативной памяти и записью в таблице базы данных:
Источник.Ссылка, запрос прочитает старое состояние объекта из базы, а не его текущие значения из оперативной памяти.Рассмотрим, как быстро определить факт изменения реквизита без выполнения тяжелых запросов. Для этого достаточно сравнить значение свойства объекта в памяти со значением реквизита, прочитанным через точку от ссылки из базы данных.
Посмотрим на пример кода в обработчике подписки на событие ПередЗаписью:
Процедура ПроверитьИзменениеПередЗаписью(Источник, Отказ) Экспорт
// Если объект новый, старых данных в базе нет
Если Источник.ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Получаем текущие значения из памяти и старые значения из базы данных
НовыйРодитель = Источник.Родитель;
СтарыйРодитель = Источник.Ссылка.Родитель;
НовыйВид = Источник.ВидНоменклатуры;
СтарыйВид = Источник.Ссылка.ВидНоменклатуры;
// Проверяем факт изменения ключевых полей
Если НовыйРодитель <> СтарыйРодитель Или НовыйВид <> СтарыйВид Тогда
// Реквизиты изменились, выполняем дальнейшую логику
КонецЕсли;
КонецПроцедуры
Обратите внимание: проверка условия Источник.Родитель <> Источник.Ссылка.Родитель является общепринятым стандартом платформы, так как читается проще и выполняется быстрее, чем отрицание с оператором равенства.
Если алгоритм определения счетов учета завязан на запрос к регистрам сведений с учетом иерархии номенклатуры, не следует обращаться в тексте запроса через точку от ссылки (например, ГДЕ Номенклатура.Родитель = ...). Вместо этого передадим новое значение родителя параметром запроса.
Разберем пример модификации запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СчетаУчета.СчетУчета,
| СчетаУчета.СчетУчетаПередачи
|ИЗ
| РегистрСведений.СчетаУчетаНоменклатуры.СрезПоследних(&Период, ) КАК СчетаУчета
|ГДЕ
| СчетаУчета.Номенклатура = &РодительНоменклатуры";
Запрос.УстановитьПараметр("Период", ТекущаяДата());
// Передаем значение из памяти объекта, а не из ссылки базы
Запрос.УстановитьПараметр("РодительНоменклатуры", Источник.Родитель);
Результат = Запрос.Выполнить();
В типовых конфигурациях (например, УПП 1.3 или ERP) правила определения счетов учета могут использовать сложные соединения по иерархии. Если алгоритм требует наличия ссылки и структуры реквизитов единым набором, сформируем таблицу значений в оперативной памяти и передадим ее во временную таблицу запроса.
Рассмотрим реализацию:
ТЗ_Объект = Новый ТаблицаЗначений;
ТЗ_Объект.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ_Объект.Колонки.Добавить("Родитель", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ_Объект.Колонки.Добавить("ВидНоменклатуры", Новый ОписаниеТипов("СправочникСсылка.ВидыНоменклатуры"));
СтрокаТЗ = ТЗ_Объект.Добавить();
СтрокаТЗ.Ссылка = Источник.Ссылка;
СтрокаТЗ.Родитель = Источник.Родитель;
СтрокаТЗ.ВидНоменклатуры = Источник.ВидНоменклатуры;
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Ссылка КАК Номенклатура,
| ТЗ.Родитель КАК Родитель,
| ТЗ.ВидНоменклатуры КАК ВидНоменклатуры
|ПОМЕСТИТЬ ВТ_ТекущиеДанные
|ИЗ
| &ТЗ_Объект КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ_Объект", ТЗ_Объект);
Запрос.Выполнить();
// Далее соединяем ВТ_ТекущиеДанные с регистрами сведений
Когда необходимо сравнить результаты стандартных типовых функций расчета до и после записи без ручного переписывания запросов, разделим логику на две подписки с использованием свойства ДополнительныеСвойства.
Разберем по шагам:
ПередЗаписью вычисляем старые счета учета по ссылке и сохраняем структуру в Источник.ДополнительныеСвойства.ПриЗаписи, когда измененные поля уже физически сохранены в транзакции базы данных, выполняем повторный запрос по ссылке и сравниваем новые счета со сохраненными старыми.Посмотрим на пример кода:
// Подписка 1: ПередЗаписью
Процедура НоменклатураПередЗаписью(Источник, Отказ) Экспорт
Если Источник.ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Получаем старые счета учета через типовой механизм по ссылке
СтарыеСчета = ПолучитьСчетаУчетаПоСсылке(Источник.Ссылка);
Источник.ДополнительныеСвойства.Вставить("СтарыеСчетаУчета", СтарыеСчета);
КонецПроцедуры
// Подписка 2: ПриЗаписи
Процедура НоменклатураПриЗаписи(Источник, Отказ) Экспорт
Если НЕ Источник.ДополнительныеСвойства.Свойство("СтарыеСчетаУчета") Тогда
Возврат;
КонецЕсли;
СтарыеСчета = Источник.ДополнительныеСвойства.СтарыеСчетаУчета;
// На этапе ПриЗаписи запрос по Источник.Ссылка уже видит обновленного родителя
НовыеСчета = ПолучитьСчетаУчетаПоСсылке(Источник.Ссылка);
Если СтарыеСчета <> НовыеСчета Тогда
// Счета изменились — выполняем регистрацию изменений или служебные действия
КонецЕсли;
КонецПроцедуры
Важное замечание: если проверка счетов учета может потребовать отмены записи (установка Отказ = Истина), всю логику следует реализовывать в ПередЗаписью (Способами 1–3), чтобы избежать лишних операций записи и аварийного отката транзакции.