В процессе разработки интеграционных решений и систем обмена данными в 1С перед программистом часто возникает задача оптимизации обмена. Передавать во внешние базы или файлы абсолютно все перезаписанные объекты крайне неэффективно. Пользователи очень часто открывают документы или элементы справочников просто «посмотреть», а затем закрывают их нажатием кнопки «ОК» (или «Записать и закрыть»). При этом стандартное свойство формы Модифицированность или подписка на событие ПриЗаписи срабатывают в штатном режиме, фиксируя факт записи, даже если ни один реквизит не был отредактирован.
В этой статье мы подробно рассмотрим, как решить эту проблему в конфигурациях на платформе 1С:Предприятие (включая классические решения вроде «Управление торговлей 10.3» и современные конфигурации). Мы разберем несколько надежных программных способов выявления реальных изменений в объекте, а также исправим популярную ошибку совместного доступа к файлам, которая часто возникает при выгрузке данных.
---
Самый производительный и правильный с точки зрения архитектуры платформы 1С способ — это сравнение реквизитов объекта в памяти с данными, которые еще сохранены в базе данных, непосредственно в обработчике ПередЗаписью.
Выясним причину, почему нельзя просто обратиться к старым реквизитам через точку вида Ссылка.Контрагент. Дело в том, что при обращении к реквизитам через стандартную ссылку внутри транзакции записи платформа сбрасывает кэш и выполняет полный неоптимизированный запрос к таблице базы данных, считывая абсолютно все поля объекта. Если документ содержит сотни или тысячи строк табличной части, это приведет к серьезному снижению производительности.
Правильным подходом является использование объекта Запрос для получения только тех полей, изменение которых действительно критично для вашей системы обмена. Разберем этот метод по шагам на примере документа.
Рассмотрим пример реализации в модуле документа в обработчике ПередЗаписью:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Если это новый документ, то он гарантированно изменен
Если ЭтоНовый() Тогда
ЭтотОбъект.ДополнительныеСвойства.Вставить("ОбъектИзменен", Истина);
Возврат;
КонецЕсли;
// Запросом получаем только критически важные для обмена реквизиты из БД
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Контрагент КАК Контрагент,
| Продажи.Организация КАК Организация,
| Продажи.СуммаДокумента КАК СуммаДокумента
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Продажи
|ГДЕ
| Продажи.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ЭтотОбъект.Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
ДанныеИзменились = Ложь;
Если Выборка.Следующий() Тогда
// Сравниваем основные реквизиты шапки
Если ЭтотОбъект.Контрагент <> Выборка.Контрагент
Или ЭтотОбъект.Организация <> Выборка.Организация
Или ЭтотОбъект.СуммаДокумента <> Выборка.СуммаДокумента Тогда
ДанныеИзменились = Истина;
КонецЕсли;
Иначе
ДанныеИзменились = Истина; // На случай, если объект не найден в БД
КонецЕсли;
// Сохраняем результат во временные свойства объекта, чтобы использовать в "ПриЗаписи"
ЭтотОбъект.ДополнительныеСвойства.Вставить("ОбъектИзменен", ДанныеИзменились);
КонецПроцедуры
Если вам также необходимо проверять изменения в табличных частях, можно воспользоваться изящным приемом «балансовой сверки». Мы выгружаем табличную часть текущего объекта в таблицу значений, а табличную часть из базы данных получаем запросом. Сворачиваем обе таблицы по ключевым полям (например, Номенклатура, Количество, Цена), причем для исходных данных из базы показатели берем с противоположным знаком (умножаем на -1). После объединения и группировки у неизмененного документа в итоге должен получиться чистый ноль.
---
Если в вашей системе обмена участвует большое количество различных справочников и документов, писать индивидуальные SQL-подобные запросы для каждого из них будет трудозатратно. Проанализируем более универсальный метод — сравнение хэш-сумм сериализованных данных.
Идея заключается в следующем: мы преобразуем значимую часть текущего объекта в текстовый формат XML или JSON и сравниваем полученную строку с аналогичной строкой, сформированной для объекта, прочитанного из базы данных. Для оптимизации сравнения длинных строк мы можем использовать свертку строки в хэш-код (например, MD5).
Посмотрим на пример реализации такой универсальной функции:
Функция ОбъектДействительноИзменился(ТекущийОбъект) Экспорт
Если ТекущийОбъект.ЭтоНовый() Тогда
Возврат Истина;
КонецЕсли;
// Получаем состояние объекта из базы данных напрямую
ОбъектИзБД = ТекущийОбъект.Ссылка.ПолучитьОбъект();
Если ОбъектИзБД = Неопределено Тогда
Возврат Истина;
КонецЕсли;
// Сериализуем текущий объект в памяти
ЗаписьXML_Текущий = Новый ЗаписьXML;
ЗаписьXML_Текущий.УстановитьСтроку();
ЗаписатьXML(ЗаписьXML_Текущий, ТекущийОбъект);
СтрокаТекущий = ЗаписьXML_Текущий.Закрыть();
// Сериализуем объект, сохраненный в базе данных
ЗаписьXML_БД = Новый ЗаписьXML;
ЗаписьXML_БД.УстановитьСтроку();
ЗаписатьXML(ЗаписьXML_БД, ОбъектИзБД);
СтрокаБД = ЗаписьXML_БД.Закрыть();
// Если XML-структуры не совпадают, значит, были внесены изменения
Возврат (СтрокаТекущий <> СтрокаБД);
КонецФункции
Важное примечание: Этот метод чрезвычайно надежен, так как он автоматически отслеживает любые изменения во всех реквизитах и абсолютно всех табличных частях без необходимости ручного написания кода. Однако учитывайте, что для очень тяжелых документов чтение объекта из БД через ПолучитьОбъект() и его XML-сериализация могут потреблять дополнительную оперативную память.
---
Если цель вашей обработки состоит в генерации текстовых файлов выгрузки (например, XML, JSON или CSV) и последующей их отправке на FTP или в другую систему, можно использовать подход с кэшированием результатов. Мы можем проверять изменения не на уровне объектов 1С, а непосредственно на уровне сформированных файлов.
Для этого в конфигурации создается вспомогательный регистр сведений КэшВыгруженныхОбъектов со следующей структурой:
Объект (Тип: ЛюбаяСсылка)ХэшФайла (Тип: Строка, длина 32 - для MD5) или ТекстВыгрузки (Тип: ХранилищеЗначения или Строка неограниченной длины).Разберем по шагам алгоритм работы выгрузки с использованием кэша:
---
При реализации выгрузки во внешние файлы разработчики часто сталкиваются с технической проблемой. Например, при попытке переименовать или переместить только что созданный файл выгрузки с помощью метода ПереместитьФайл() платформа выдает ошибку:
Ошибка совместного доступа к файлу 'W:\Обмен\XXX_new.company'
Проанализируем ситуацию и выясним причину ее возникновения. Данное исключение указывает на то, что файл в текущий момент времени заблокирован операционной системой, поскольку процесс 1С:Предприятия все еще удерживает открытый дескриптор (поток) этого файла. Это происходит из-за того, что объект, производивший запись данных в файл, не был закрыт должным образом перед вызовом операций перемещения или копирования.
Чтобы избежать этой ошибки, всегда обязательно закрывайте потоки записи. Посмотрим на типичный пример ошибочного кода и его правильный исправленный вариант.
Неправильный код (вызывает ошибку блокировки):
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("W:\Обмен\export_new.xml");
ЗаписатьXML(ЗаписьXML, ДокументОбъект);
// Поток не закрыт! Файл заблокирован операционной системой.
// Попытка переименования вызовет ошибку совместного доступа:
ПереместитьФайл("W:\Обмен\export_new.xml", "W:\Обмен\export_modified.xml");
Правильный и безопасный вариант кода:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("W:\Обмен\export_new.xml");
ЗаписатьXML(ЗаписьXML, ДокументОбъект);
// Обязательно явно закрываем поток записи, чтобы освободить дескриптор файла!
ЗаписьXML.Закрыть();
// Теперь операционная система полностью освободила файл, и мы можем его переименовать:
Попытка
ПереместитьФайл("W:\Обмен\export_new.xml", "W:\Обмен\export_modified.xml");
Исключение
Сообщить("Не удалось переместить файл по причине: " + ОписаниеОшибки());
КонецПопытки;
Аналогичное правило «обязательного закрытия» применимо ко всем объектам работы с файловыми потоками в 1С: ЗаписьТекста (метод Закрыть()), ЗаписьДанных, а также к чтению файлов (ЧтениеXML, ЧтениеТекста).
Применяя данные методики на практике, вы сможете полностью исключить отправку «пустых» обновлений в смежные информационные системы, существенно снизите нагрузку на сетевые каналы передачи данных и обеспечите надежную, отказоустойчивую работу ваших интеграционных шлюзов.