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