Почему некорректно работает заполнение документов в цикле при разборе XML и как избежать дублей?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Финансы и профессиональные услуги
← На главную

При программной загрузке данных из XML-файлов в 1С:Предприятие 8 начинающие разработчики часто сталкиваются с проблемой "размножения" записей. Ситуация, когда вместо ожидаемых 1300 документов в базе оказывается 13 000 и более, обычно связана с фундаментальным непониманием того, как работает объект ЧтениеXML. В этой статье мы подробно разберем, почему возникают дубли, как правильно структурировать цикл обработки узлов и как реализовать надежную проверку на уникальность загружаемых данных.

Разбор основной ошибки: почему документов создается слишком много?

Давайте проанализируем ситуацию: объект ЧтениеXML при вызове метода Прочитать() последовательно проходит через каждый узел файла. Узел — это не только сам тег (начало или конец), но и текст внутри него, и даже пробельные символы (отступы).

Рассмотрим типичную ошибку в логике кода:


Пока Чтение.Прочитать() Цикл
    Если Чтение.ТипУзла = ТипУзлаXML.Текст Тогда
        // ... присвоение значений переменным ...
        Если Касса = "Расход" Тогда
            НовыйДокумент = Документы.Расходы.СоздатьДокумент();
            // ... заполнение и запись ...
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

В чем заключается проблема: Представим, что в одном блоке Item у вас 10 тегов (дата, сумма, город и т.д.). Для каждого тега существует текстовый узел. Если условие Касса = "Расход" выполнилось один раз, переменная Касса сохраняет это значение до тех пор, пока не будет считано новое. В результате программа создает новый документ на каждом текстовом узле внутри одного и того же блока данных. Если в блоке 10 полей, вы получите 10 одинаковых документов вместо одного.

Решение №1: Правильная структура цикла через отслеживание начала элемента

Чтобы избежать дублирования, мы должны четко определить границы одного объекта (например, тег Item). Мы создаем объект только тогда, когда достигнут конец описания элемента, или собираем все данные внутри блока и записываем их один раз. Рассмотрим правильный подход с использованием вложенного цикла для обработки конкретного элемента.

Разберем алгоритм действий:

  1. Находим начало узла Item.
  2. Внутри этого узла запускаем вложенный цикл, который читает все свойства именно этого элемента.
  3. Как только встречаем закрывающий тег /Item, выполняем запись собранных данных в документ.
  4. Сбрасываем значения временных переменных для следующей итерации.

Пример реализации с использованием вложенного чтения:


Пока Чтение.Прочитать() Цикл
    // Ищем начало блока данных
    Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "Item" Тогда
        
        // Обнуляем переменные перед чтением нового блока
        ВнешнийКод = ""; ДатаОперации = '00010101'; Сумма = 0; Касса = "";
        
        Пока Чтение.Прочитать() Цикл
            ТипУзла = Чтение.ТипУзла;
            ИмяУзла = Чтение.Имя;
            
            // Если дошли до конца блока Item - выходим из вложенного цикла
            Если ТипУзла = ТипУзлаXML.КонецЭлемента И ИмяУзла = "Item" Тогда
                Прервать;
            КонецЕсли;
            
            // Читаем значения внутри тегов
            Если ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
                ТекущийТег = ИмяУзла;
                // Читаем следующий узел, который должен быть текстом
                Чтение.Прочитать();
                Если Чтение.ТипУзла = ТипУзлаXML.Текст Тогда
                    Значение = Чтение.Значение;
                    Если ТекущийТег = "external" Тогда ВнешнийКод = Значение;
                    ИначеЕсли ТекущийТег = "total" Тогда Сумма = Число(Значение);
                    ИначеЕсли ТекущийТег = "cashbox" Тогда Касса = Значение;
                    // ... остальные поля ...
                    КонецЕсли;
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;
        
        // Только здесь, когда весь Item прочитан, создаем документ
        Если Касса = "Расход" Тогда
            ОбработатьЗаписьДокумента(ВнешнийКод, ДатаОперации, Сумма);
        КонецЕсли;
        
    КонецЕсли;
КонецЦикла;

Решение №2: Проверка на существующие дубликаты в базе

Даже при правильном цикле вы можете запустить загрузку дважды и получить дубли. Чтобы этого не произошло, необходимо выполнять поиск уже существующего документа по уникальному идентификатору из файла (поле external в вашем примере).

Проанализируем, как реализовать проверку эффективно:

  1. Используйте метод НайтиПоРеквизиту для поиска документа.
  2. Важно: Реквизит, по которому идет поиск (например, ВнешнийКод), должен быть индексирован в метаданных конфигурации, иначе поиск будет тормозить всю загрузку.

Пример кода для проверки:


СсылкаНаДокумент = Документы.Расходы.НайтиПоРеквизиту("ВнешнийКод", ВнешнийКод);

Если СсылкаНаДокумент.Пустая() Тогда
    НовыйДокумент = Документы.Расходы.СоздатьДокумент();
    НовыйДокумент.ВнешнийКод = ВнешнийКод;
Иначе
    // Если нужно обновлять данные в существующем документе:
    НовыйДокумент = СсылкаНаДокумент.ПолучитьОбъект();
КонецЕсли;

// Заполнение реквизитов...
НовыйДокумент.Записать();

Решение №3: Оптимизация работы со справочниками (Кэширование)

В вашем исходном коде на каждой итерации вызывается НайтиПоНаименованию для городов и MCC. Если в файле 1300 записей и 50 городов, 1С сделает 1300 тяжелых запросов к базе данных. Проанализируем, как ускорить этот процесс с помощью объекта Соответствие.

Идея заключается в том, чтобы хранить уже найденные ссылки в оперативной памяти:


// Перед циклом создаем кэш
КэшГородов = Новый Соответствие;

// Внутри цикла, где обрабатываем город
НаименованиеГорода = Чтение.Значение;
СсылкаГород = КэшГородов.Получить(НаименованиеГорода);

Если СсылкаГород = Неопределено Тогда
    // В кэше нет, ищем в базе
    СсылкаГород = Справочники.Города.НайтиПоНаименованию(НаименованиеГорода);
    Если СсылкаГород.Пустая() Тогда
        // Создаем новый город, если не нашли
        НовыйГород = Справочники.Города.СоздатьЭлемент();
        НовыйГород.Наименование = НаименованиеГорода;
        НовыйГород.Записать();
        СсылкаГород = НовыйГород.Ссылка;
    КонецЕсли;
    // Добавляем в кэш, чтобы в следующий раз не искать в базе
    КэшГородов.Вставить(НаименованиеГорода, СсылкаГород);
КонецЕсли;

НовыйДокумент.Город = СсылкаГород;

Решение №4: Использование транзакций для надежности и скорости

При загрузке большого объема данных (тысячи записей) рекомендуется использовать механизм транзакций. Это решает две задачи: скорость (запись пачкой происходит в разы быстрее) и целостность (если на 500-й записи произойдет ошибка, база не останется наполовину заполненной мусором).

Рассмотрим схему применения:


НачатьТранзакцию();
Попытка
    // Весь ваш цикл чтения XML и записи документов здесь
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    Сообщить("Ошибка при загрузке: " + ОписаниеОшибки());
КонецПопытки;

Подведем итоги

Для корректного разбора XML и создания документов без дублей следуйте этим правилам:

Применяя эти подходы, вы превратите нестабильную загрузку с дублями в надежный и быстрый инструмент интеграции.

← На главную