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