В практике разработки обменов данными в среде 1С:Предприятие часто встречается коварная проблема: процесс выгрузки завершается, файл создается, но при попытке его прочитать возникает фатальная ошибка "Extra content at the end of the document". При детальном рассмотрении оказывается, что в конце файла просто отсутствует закрывающий тег корневого элемента (например, </Выгрузка>). В этой статье мы разберем, почему метод УдалитьРегистрациюИзменений может быть косвенно связан с этой проблемой, и как правильно построить алгоритм выгрузки, чтобы избежать потери данных.
Рассмотрим ситуацию, когда код выглядит логично: мы выбираем данные, записываем их в XML и тут же удаляем регистрацию изменений для этого объекта в плане обмена. Однако, если файл получается «обрубленным», это сигнализирует о том, что выполнение кода прервалось до того, как сработали методы ЗаписатьКонецЭлемента() и Закрыть(). Проанализируем основные причины такого поведения:
УдалитьРегистрациюИзменений вызывает ошибку (например, конфликт блокировок в СУБД), и этот код не обернут в блок Попытка...Исключение, выполнение всей процедуры немедленно прекращается. Платформа переходит к обработке ошибки, и строки кода, отвечающие за финализацию XML, игнорируются.Метод ПланыОбмена.УдалитьРегистрациюИзменений() — это не просто «галочка» в памяти. Это полноценная операция записи в таблицу регистрации изменений (таблицу с префиксом _Chng). Рассмотрим, какие опасности она таит:
Блокировки данных: Когда мы удаляем регистрацию, СУБД накладывает блокировки на соответствующие строки. Если в этот же момент другой процесс (например, фоновое перепроведение документов) пытается зарегистрировать изменения для тех же объектов, возникает конфликт. В лучшем случае 1С подождет, в худшем — выбросит ошибку «Конфликт блокировок».
Транзакционная нагрузка: Вызов этого метода внутри цикла для каждого объекта создает множество мелких транзакций. Это значительно замедляет выгрузку. Проанализируем пример «опасного» кода:
Выборка = ПланыОбмена.ВыбратьИзменения(Узел, НомерСообщения);
Пока Выборка.Следующий() Цикл
// Запись данных в XML
ЗаписатьДанныеВXML(Выборка.Объект);
// Удаление регистрации - ТУТ МОЖЕТ БЫТЬ ОШИБКА БЛОКИРОВКИ
ПланыОбмена.УдалитьРегистрациюИзменений(Узел, Выборка.Объект);
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.Закрыть();
Если на десятом объекте из тысячи возникнет блокировка, файл останется без закрывающего тега, а изменения для первых девяти объектов уже будут удалены из плана обмена. Это приведет к рассинхронизации данных между базами.
Выясним, есть ли смысл заключать удаление регистрации в попытку. Ответ: однозначно да, если вы хотите, чтобы формирование файла завершилось во что бы то ни стало. Но нужно понимать последствия. Посмотрим на правильную реализацию:
Попытка
ПланыОбмена.УдалитьРегистрациюИзменений(Узел, СсылкаНаОбъект);
Исключение
ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка, , ,
"Не удалось снять с регистрации: " + ОписаниеОшибки());
// Здесь мы решаем: продолжать ли запись файла или прервать процесс корректно
КонецПопытки;
Использование ОписаниеОшибки() в блоке исключения позволит вам увидеть реальную причину в Журнале регистрации, даже если визуально всё кажется исправным. Если там фигурирует «Конфликт блокировок», значит, проблема во взаимодействии с другими процессами.
Рассмотрим более надежный подход. Чтобы гарантировать целостность XML-файла, операции с регистрацией изменений следует вынести за пределы процесса записи файла. Разберем по шагам наиболее стабильный алгоритм:
ЗаписьXML.Закрыть(). Теперь файл гарантированно цел и готов к отправке.Проанализируем пример реализации такого подхода:
// 1. Подготовка
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ПутьКФайлу);
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("Выгрузка");
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(Узел, НомерСообщения);
// 2. Цикл записи данных (без удаления регистрации!)
Пока ВыборкаИзменений.Следующий() Цикл
ДанныеОбъекта = ВыборкаИзменений.Объект;
// ... логика записи ...
КонецЦикла;
// 3. Завершение работы с файлом
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.Закрыть();
// 4. Безопасное удаление регистрации изменений
Попытка
// Удаляем регистрацию сразу для всего сообщения
ПланыОбмена.УдалитьРегистрациюИзменений(Узел, НомерСообщения);
Исключение
Сообщить("Файл сформирован, но регистрация изменений не удалена: " + ОписаниеОшибки());
КонецПопытки;
Почему это лучше? Во-первых, если произойдет сбой при удалении регистрации, сам файл выгрузки уже будет корректно сформирован и закрыт. Его можно будет загрузить в приемнике. Во-вторых, удаление регистрации по номеру сообщения работает значительно быстрее и создает меньше блокировок, чем удаление по одному объекту в цикле.
Проанализируем ситуацию, когда даже перенос кода не помогает. В этом случае стоит обратить внимание на системные ресурсы:
ПланыОбмена.ВыбратьИзменения() с передачей номера сообщения. Это позволит «заморозить» состав выгрузки и избежать проблем, если в процессе выгрузки данные в базе будут меняться.УдалитьРегистрациюИзменений для всех типов объектов, входящих в план обмена. Иногда нехватка прав на один справочник может прервать весь процесс.Таким образом, мы выяснили, что ошибка в методе УдалитьРегистрациюИзменений действительно может привести к «битому» XML-файлу из-за нештатного прерывания выполнения кода. Соблюдение правильной последовательности действий (сначала закрыть файл, потом удалить регистрацию) и использование блоков обработки исключений сделает ваш обмен данными гораздо более отказоустойчивым.