При настройке обмена данными между конфигурациями 1С с помощью инструмента 1С:Конвертация данных 2.1 (КД2) разработчики часто сталкиваются с классической задачей: как обновить объект в базе-приемнике, но при этом гарантировать, что важные заполненные реквизиты (например, телефон контрагента, ИНН, внутренний код) не будут затерты пустыми значениями, если в базе-источнике эти поля по какой-то причине оказались не заполнены.
Поведение КД2 по умолчанию абсолютно прямолинейно — оно перезаписывает все сопоставленные свойства объекта. Если из источника пришло пустое значение, приемник послушно очистит соответствующий реквизит у себя. В этой статье мы подробно проанализируем различные подходы к решению этой проблемы, начиная от простых программных обработчиков в приемнике и заканчивая максимально производительными методами оптимизации обмена на стороне источника.
Давайте сначала разберем интерактивные возможности настройки правил конвертации свойств (ПКС). В КД2 действительно существует флаг «Не замещать значение свойства у существующих объектов в приёмнике». Но решает ли он нашу задачу?
Выясним причину, почему эта галка нам не подходит. Данный флаг работает безусловно: если объект в приемнике уже существует, то КД2 вообще никогда не будет обновлять этот реквизит, даже если из источника пришло новое, актуальное и заполненное значение. Наша же задача гораздо тоньше: мы хотим обновлять реквизит, если в источнике появилось новое значение, но не хотим очищать его, если в источнике реквизит пустой.
Поэтому стандартная галка в ПКС здесь бессильна, и нам придется прибегнуть к написанию несложного программного кода.
Рассмотрим наиболее производительный и правильный с точки зрения архитектуры способ. Вместо того чтобы обрабатывать пустые значения в базе-приемнике, мы можем вообще не выгружать пустое свойство из базы-источника.
Для реализации этого подхода откроем настройку нужного правила конвертации свойств (ПКС) и перейдем в обработчик события Перед выгрузкой. Напишем там следующий код:
Если НЕ ЗначениеЗаполнено(Источник.ИмяРеквизита) Тогда
Отказ = Истина;
КонецЕсли;
Проанализируем ситуацию и разберем по шагам, почему этот простой код работает так эффективно:
Отказ = Истина.Преимущества этого метода колоссальны. Во-первых, мы уменьшаем размер XML-файла выгрузки. Во-вторых, мы полностью избавляем базу-приемник от необходимости выполнять дополнительные ресурсоемкие обращения к СУБД для проверки старого состояния объекта.
Иногда логику обмена требуется реализовать исключительно на стороне приемника (например, когда правила выгрузки из источника закрыты для редактирования или схема обмена жестко зафиксирована). В этом случае мы можем восстановить старое значение непосредственно в процессе загрузки объекта.
Посмотрим на пример реализации. В правиле конвертации объектов (ПКО) для нашего объекта перейдем в обработчик события После загрузки. Напишем код восстановления значения:
Если ОбъектНайден Тогда
Если ЗначениеЗаполнено(Ссылка.НашРеквизит) И НЕ ЗначениеЗаполнено(Объект.НашРеквизит) Тогда
Объект.НашРеквизит = Ссылка.НашРеквизит;
КонецЕсли;
КонецЕсли;
Разберем, как работает эта конструкция:
ОбъектНайден — это булевый флаг КД2, который сообщает, существовал ли загружаемый объект в базе-приемнике до начала текущего сеанса обмена. Если объект создается впервые, восстанавливать нам нечего, и код внутри условия не выполняется.Объект содержит воссозданный в оперативной памяти объект (еще не записанный в базу данных), свойства которого заполнены значениями из XML-файла.Ссылка представляет собой ссылку на оригинальный элемент, хранящийся непосредственно в базе данных приемника.ЗначениеЗаполнено(Ссылка.НашРеквизит)), а в загруженном объекте оно пустое (НЕ ЗначениеЗаполнено(Объект.НашРеквизит)), то мы принудительно возвращаем старому объекту его исходное значение.Важное предупреждение о производительности! При использовании обращения через точку к свойствам ссылки (Ссылка.НашРеквизит) платформа 1С выполняет неявный SQL-запрос к базе данных для получения этого значения. Если вы переносите десятки тысяч объектов за один сеанс обмена, подобные обращения могут критически замедлить загрузку (проблема «N+1 запросов»).
Если вам критически важно использовать именно этот способ на больших объемах данных, рекомендуется предварительно кэшировать старые значения реквизитов с помощью пакетного запроса в обработчике При загрузке или оптимизировать чтение через функцию ОбщегоНазначения.ЗначениеРеквизитаОбъекта(), если она доступна в вашей конфигурации.
Описанные выше способы отлично подходят для реквизитов шапки документов или справочников. Но что делать, если речь идет о реквизитах внутри строк табличных частей (ТЧ)?
Выясним техническую особенность работы КД2 с табличными частями. При загрузке объекта табличные части по умолчанию очищаются полностью и заполняются заново на основании пришедших данных. Способ с Отказ = Истина в ПКС строки ТЧ здесь не сработает должным образом, так как строка будет перезаписана целиком, и пропущенная колонка просто окажется пустой.
Чтобы обойти это ограничение, нам придется временно сохранить («запомнить») старое состояние табличной части базы-приемника. Рассмотрим пошаговый алгоритм решения:
Шаг 1. В обработчике ПКО При загрузке прочитаем старую табличную часть из базы данных и сохраним ее во временную структуру параметров объекта:
Если ОбъектНайден Тогда
// Сохраняем исходную ТЧ в структуру параметров загружаемого объекта
Параметры.Вставить("СтараяТабличнаяЧасть", Ссылка.Товары.Выгрузить());
КонецЕсли;
Шаг 2. В обработчике ПКО После загрузки сопоставим новые строки табличной части со старыми сохраненными строками (например, по ключевому полю Номенклатура) и восстановим пустые колонки:
Если ОбъектНайден Тогда
ТаблицаСтарыхДанных = Неопределено;
Если Параметры.Свойство("СтараяТабличнаяЧасть", ТаблицаСтарыхДанных) Тогда
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
// Ищем строку по ключевому полю в старых данных
ПараметрыОтбора = Новый Структура("Номенклатура", СтрокаТЧ.Номенклатура);
НайденныеСтроки = ТаблицаСтарыхДанных.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
СтараяСтрока = НайденныеСтроки[0];
// Если новое значение реквизита в строке пустое, возвращаем старое
Если НЕ ЗначениеЗаполнено(СтрокаТЧ.Спецификация) И ЗначениеЗаполнено(СтараяСтрока.Спецификация) Тогда
СтрокаТЧ.Спецификация = СтараяСтрока.Спецификация;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЕсли;
Этот изящный алгоритм позволяет точечно управлять заполнением реквизитов даже внутри сложных табличных частей, сохраняя целостность исторических данных в базе-приемнике.
В попытках быстро решить проблему затирания данных некоторые разработчики прибегают к использованию флага НеЗамещатьОбъект = Истина в обработчике При загрузке.
Проанализируем риски такого решения. Этот флаг полностью отменяет запись любых изменений в найденный объект. Если объект уже существует в базе, обмен просто проигнорирует его обновление. Помимо того, что новые полезные изменения также не попадут в базу, у этого флага есть неприятная техническая особенность в алгоритмах КД2.
Если в XML-файле один и тот же объект сначала выгружается со всеми реквизитами, а затем в других узлах на него просто передается ссылка по идентификатору (например, в качестве реквизита другого документа), то взведенный флаг НеЗамещатьОбъект у первого объекта может привести к неожиданной очистке ссылок на него в последующих загружаемых документах. Происходит это из-за особенностей работы механизма сопоставления объектов КД2 «по ссылкам». Поэтому всегда безопаснее и надежнее использовать точечное управление свойствами, которое мы подробно разобрали выше.
Подведем итоги нашего исследования: