При разработке механизмов загрузки данных из внешних источников (XML, JSON, Excel) разработчики часто сталкиваются с типовой дилеммой: что делать, если входящий документ уже присутствует в информационной базе? Попытка решать этот вопрос «в лоб» — задавать интерактивный вопрос пользователю на клиенте по каждому найденному совпадению или удалять старый документ перед созданием нового — приводит к падению производительности, блокировкам интерфейса и нарушению ссылочной целостности базы данных.
Разберем по шагам, как построить правильную архитектуру загрузки, избежать дублирования кода между клиентом и сервером и корректно перезаписывать объекты.
Проанализируем ключевые архитектурные ошибки, которые часто допускаются на начальном этапе проектирования подобных механизмов:
УникальныйИдентификатор (GUID). В результате все внешние ссылки на старый документ (в подчиненных документах, регистрах сведений, движениях регистров накопления и бухгалтерии) превратятся в «битые» ссылки («Объект не найден»). Правильный подход — обновлять существующий объект по ссылке.РежимИспользованияМодальности = НеИспользовать). Модальные диалоги заблокированы, а вызов асинхронного метода ПоказатьВопрос внутри стандартного цикла невозможен без создания сложной цепочки рекурсивных методов через ОписаниеОповещения. Кроме того, опрос по каждому документу сильно утомляет пользователя при импорте сотен позиций.Рассмотрим, как оптимизировать парсинг и проверку наличия данных. Вместо массива структур сформируем на сервере объект ТаблицаЗначений. В ней зафиксируем все ключевые поля для поиска: номер, дату (или период), организацию, а также уникальный идентификатор из внешнего файла (если он передается в XML).
Затем передадим эту таблицу в качестве параметра в запрос. Это позволит за одно обращение к СУБД найти все существующие документы:
// В серверном контексте формируем типизированную таблицу загрузки
ТаблицаЗагрузки = Новый ТаблицаЗначений;
ТаблицаЗагрузки.Колонки.Добавить("Номер", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(30)));
ТаблицаЗагрузки.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));
ТаблицаЗагрузки.Колонки.Добавить("Организация", Новый ОписаниеТипов("СправочникСсылка.Организации"));
ТаблицаЗагрузки.Колонки.Добавить("ИдентификаторXML", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(50)));
// После заполнения таблицы из XML передаем ее в запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Вход.Номер КАК Номер,
| Вход.Дата КАК Дата,
| Вход.Организация КАК Организация,
| Вход.ИдентификаторXML КАК ИдентификаторXML
|ПОМЕСТИТЬ ВТ_Загрузка
|ИЗ
| &ТаблицаЗагрузки КАК Вход
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Номер КАК Номер,
| ВТ.Дата КАК Дата,
| ВТ.Организация КАК Организация,
| ВТ.ИдентификаторXML КАК ИдентификаторXML,
| Реализация.Ссылка КАК НайденныйДокумент,
| ВЫБОР
| КОГДА Реализация.Ссылка ЕСТЬ NULL ТОГДА ЛОЖЬ
| ИНАЧЕ ИСТИНА
| КОНЕЦ КАК ЭтоДубль
|ИЗ
| ВТ_Загрузка КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация
| ПО ВТ.Номер = Реализация.Номер
| И ВТ.Организация = Реализация.Организация
| И НАЧАЛОПЕРИОДА(ВТ.Дата, ГОД) = НАЧАЛОПЕРИОДА(Реализация.Дата, ГОД)";
Запрос.УстановитьПараметр("ТаблицаЗагрузки", ТаблицаЗагрузки);
РезультатПроверки = Запрос.Выполнить().Выгрузить();
Выясним, как избавиться от диалогов в цикле. Вместо опроса пользователя по каждому документу выведем список конфликтных позиций в отдельную форму предварительного просмотра или в табличную часть текущей формы обработки.
Разберем рекомендуемый порядок работы интерфейса:
ПоместитьВоВременноеХранилище. Адрес хранилища передается на клиент.Посмотрим, как выполнить саму загрузку и избежать создания дублей или повреждения связанных данных. Если пользователь выбрал вариант перезаписи, мы получаем объект существующего документа по найденной ссылке через Ссылка.ПолучитьОбъект(). Если документ новый — создаем его через метод СоздатьДокумент() менеджера документов.
Для предотвращения лишних платформенных проверок, автонумерации и срабатывания интерактивных обработчиков событий при загрузке обязательно взводим флаг ОбменДанными.Загрузка:
// В серверной процедуре обработки
Для Каждого Стр Из ТаблицаРешений Цикл
Если Стр.ЭтоДубль И Не Стр.Перезаписать Тогда
// Пользователь отказался обновлять документ - пропускаем
Продолжить;
КонецЕсли;
Если ЗначениеЗаполнено(Стр.НайденныйДокумент) Тогда
// Модифицируем существующий документ, сохраняя его GUID и ссылки
ДокументОбъект = Стр.НайденныйДокумент.ПолучитьОбъект();
ДокументОбъект.Товары.Очистить();
Иначе
// Создаем новый документ
ДокументОбъект = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
КонецЕсли;
// Включаем режим загрузки для отключения прикладных проверок
ДокументОбъект.ОбменДанными.Загрузка = Истина;
// Заполняем реквизиты документа из ранее распарсенных данных
ДокументОбъект.Номер = Стр.Номер;
ДокументОбъект.Дата = Стр.Дата;
ДокументОбъект.Организация = Стр.Организация;
// Заполняем табличные части...
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЦикла;
Проанализируем ситуацию, когда обработка выполняется в многопользовательском режиме. Между моментом, когда система проверила наличие дублей, и моментом, когда пользователь подтвердил перезапись, может пройти несколько минут. За это время другой пользователь или регламентное задание может создать документ с такими же реквизитами.
Для гарантированной защиты от коллизий финальную запись данных выполняем в явной транзакции с предварительной установкой управляемой блокировки через объект БлокировкаДанных по пространству ключевых полей (например, по организации и номеру).
Отказ от модальных вопросов в цикле в пользу единого табличного интерфейса сопоставления снимает проблему дублирования кода между клиентом и сервером. Использование метода ПолучитьОбъект() вместо связки «удаление + создание нового» гарантирует целостность базы данных и сохраняет внутренние связи конфигурации.