При программной загрузке данных из XML-файлов в 1С:Предприятие 8 начинающие разработчики часто сталкиваются с проблемой "размножения" записей. Ситуация, когда вместо ожидаемых 1300 документов в базе оказывается 13 000 и более, обычно связана с фундаментальным непониманием того, как работает объект ЧтениеXML. В этой статье мы подробно разберем, почему возникают дубли, как правильно структурировать цикл обработки узлов и как реализовать надежную проверку на уникальность загружаемых данных.
Давайте проанализируем ситуацию: объект ЧтениеXML при вызове метода Прочитать() последовательно проходит через каждый узел файла. Узел — это не только сам тег (начало или конец), но и текст внутри него, и даже пробельные символы (отступы).
Рассмотрим типичную ошибку в логике кода:
Пока Чтение.Прочитать() Цикл
Если Чтение.ТипУзла = ТипУзлаXML.Текст Тогда
// ... присвоение значений переменным ...
Если Касса = "Расход" Тогда
НовыйДокумент = Документы.Расходы.СоздатьДокумент();
// ... заполнение и запись ...
КонецЕсли;
КонецЕсли;
КонецЦикла;
В чем заключается проблема: Представим, что в одном блоке Item у вас 10 тегов (дата, сумма, город и т.д.). Для каждого тега существует текстовый узел. Если условие Касса = "Расход" выполнилось один раз, переменная Касса сохраняет это значение до тех пор, пока не будет считано новое. В результате программа создает новый документ на каждом текстовом узле внутри одного и того же блока данных. Если в блоке 10 полей, вы получите 10 одинаковых документов вместо одного.
Чтобы избежать дублирования, мы должны четко определить границы одного объекта (например, тег Item). Мы создаем объект только тогда, когда достигнут конец описания элемента, или собираем все данные внутри блока и записываем их один раз. Рассмотрим правильный подход с использованием вложенного цикла для обработки конкретного элемента.
Разберем алгоритм действий:
Item./Item, выполняем запись собранных данных в документ.Пример реализации с использованием вложенного чтения:
Пока Чтение.Прочитать() Цикл
// Ищем начало блока данных
Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "Item" Тогда
// Обнуляем переменные перед чтением нового блока
ВнешнийКод = ""; ДатаОперации = '00010101'; Сумма = 0; Касса = "";
Пока Чтение.Прочитать() Цикл
ТипУзла = Чтение.ТипУзла;
ИмяУзла = Чтение.Имя;
// Если дошли до конца блока Item - выходим из вложенного цикла
Если ТипУзла = ТипУзлаXML.КонецЭлемента И ИмяУзла = "Item" Тогда
Прервать;
КонецЕсли;
// Читаем значения внутри тегов
Если ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
ТекущийТег = ИмяУзла;
// Читаем следующий узел, который должен быть текстом
Чтение.Прочитать();
Если Чтение.ТипУзла = ТипУзлаXML.Текст Тогда
Значение = Чтение.Значение;
Если ТекущийТег = "external" Тогда ВнешнийКод = Значение;
ИначеЕсли ТекущийТег = "total" Тогда Сумма = Число(Значение);
ИначеЕсли ТекущийТег = "cashbox" Тогда Касса = Значение;
// ... остальные поля ...
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Только здесь, когда весь Item прочитан, создаем документ
Если Касса = "Расход" Тогда
ОбработатьЗаписьДокумента(ВнешнийКод, ДатаОперации, Сумма);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Даже при правильном цикле вы можете запустить загрузку дважды и получить дубли. Чтобы этого не произошло, необходимо выполнять поиск уже существующего документа по уникальному идентификатору из файла (поле external в вашем примере).
Проанализируем, как реализовать проверку эффективно:
НайтиПоРеквизиту для поиска документа.ВнешнийКод), должен быть индексирован в метаданных конфигурации, иначе поиск будет тормозить всю загрузку.Пример кода для проверки:
СсылкаНаДокумент = Документы.Расходы.НайтиПоРеквизиту("ВнешнийКод", ВнешнийКод);
Если СсылкаНаДокумент.Пустая() Тогда
НовыйДокумент = Документы.Расходы.СоздатьДокумент();
НовыйДокумент.ВнешнийКод = ВнешнийКод;
Иначе
// Если нужно обновлять данные в существующем документе:
НовыйДокумент = СсылкаНаДокумент.ПолучитьОбъект();
КонецЕсли;
// Заполнение реквизитов...
НовыйДокумент.Записать();
В вашем исходном коде на каждой итерации вызывается НайтиПоНаименованию для городов и MCC. Если в файле 1300 записей и 50 городов, 1С сделает 1300 тяжелых запросов к базе данных. Проанализируем, как ускорить этот процесс с помощью объекта Соответствие.
Идея заключается в том, чтобы хранить уже найденные ссылки в оперативной памяти:
// Перед циклом создаем кэш
КэшГородов = Новый Соответствие;
// Внутри цикла, где обрабатываем город
НаименованиеГорода = Чтение.Значение;
СсылкаГород = КэшГородов.Получить(НаименованиеГорода);
Если СсылкаГород = Неопределено Тогда
// В кэше нет, ищем в базе
СсылкаГород = Справочники.Города.НайтиПоНаименованию(НаименованиеГорода);
Если СсылкаГород.Пустая() Тогда
// Создаем новый город, если не нашли
НовыйГород = Справочники.Города.СоздатьЭлемент();
НовыйГород.Наименование = НаименованиеГорода;
НовыйГород.Записать();
СсылкаГород = НовыйГород.Ссылка;
КонецЕсли;
// Добавляем в кэш, чтобы в следующий раз не искать в базе
КэшГородов.Вставить(НаименованиеГорода, СсылкаГород);
КонецЕсли;
НовыйДокумент.Город = СсылкаГород;
При загрузке большого объема данных (тысячи записей) рекомендуется использовать механизм транзакций. Это решает две задачи: скорость (запись пачкой происходит в разы быстрее) и целостность (если на 500-й записи произойдет ошибка, база не останется наполовину заполненной мусором).
Рассмотрим схему применения:
НачатьТранзакцию();
Попытка
// Весь ваш цикл чтения XML и записи документов здесь
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщить("Ошибка при загрузке: " + ОписаниеОшибки());
КонецПопытки;
Для корректного разбора XML и создания документов без дублей следуйте этим правилам:
Item).НайтиПоРеквизиту по уникальному ID из внешней системы.Соответствие для хранения ссылок на часто повторяющиеся элементы (Города, Категории, MCC).Число() и к датам через XMLЗначение(Тип("Дата"), ...) для исключения ошибок формата.Применяя эти подходы, вы превратите нестабильную загрузку с дублями в надежный и быстрый инструмент интеграции.