При разработке сложных обменов данными в Конвертации Данных 2.x (КД 2) программисты периодически сталкиваются с поведением платформы «1С:Предприятие», которое кажется необъяснимым и даже мистическим. Один из ярких примеров — ситуация, когда программный код корректно записывает данные только в том случае, если в нем присутствует отладочный вывод через процедуру Сообщить(). Стоит закомментировать или удалить строку вывода сообщения, как алгоритм перестает отрабатывать.
В рамках этой статьи мы подробно разберем, почему возникает этот эффект, как он связан с платформенным методом Модифицированность(), кэшированием объектов базы данных и неявным приведением типов, а также рассмотрим архитектурно правильные способы реализации подобных алгоритмов.
Рассмотрим сценарий: выполняется перенос данных из старой конфигурации «Управление торговлей 9.0» в «Комплексную автоматизацию 2.0». Нам необходимо перенести свойства номенклатуры в дополнительные реквизиты. Для этого в процессе загрузки в обработчике ПослеЗагрузки правил конвертации объектов (ПКО) вызывается алгоритм, который должен добавлять свойства в табличную часть справочника НаборыДополнительныхРеквизитовИСведений.
Первоначальный вариант алгоритма выглядел следующим образом:
Объект = Параметры.ПараметрыАлгоритмов.Объект;
// Проверим, что реквизит указан в наборе доп. реквизитов номенклатуры
НаборОб = Объект.НаборСвойств.ПолучитьОбъект();
Сообщить("НаборОб = " + НаборОб); // Отладочный вывод
Если НаборОб.ДополнительныеРеквизиты.Найти(Ссылка, "Свойство") = Неопределено Тогда
// Добавим реквизит в набор
СтрНаборОб = НаборОб.ДополнительныеРеквизиты.Добавить();
СтрНаборОб.Свойство = Ссылка;
КонецЕсли;
Если НаборОб.Модифицированность Тогда
Сообщить("Записываем");
НаборОб.Записать();
КонецЕсли;
Аномалия: В таком виде алгоритм работает безошибочно. Но если закомментировать строку Сообщить("НаборОб = " + НаборОб);, то условие проверки модифицированности Если НаборОб.Модифицированность Тогда перестает выполняться, и измененный объект не записывается в базу данных.
Выясним истинную причину этого «мистического» поведения платформы.
Первое, на что необходимо обратить внимание — это обращение к свойству/методу Модифицированность. В платформе «1С:Предприятие 8» существует фундаментальное различие в использовании этого термина в зависимости от контекста:
Модифицированность — это свойство. Оно доступно для чтения и записи. Синтаксис: ЭтаФорма.Модифицированность = Истина;.СправочникОбъект или ДокументОбъект, Модифицированность() — это метод (функция). Синтаксис требует обязательного использования круглых скобок: Если Объект.Модифицированность() Тогда. Метод возвращает Булево, показывая, отличаются ли данные объекта в памяти от данных в базе.Поскольку загрузка данных в КД 2.x всегда происходит на сервере, мы работаем напрямую с объектом базы данных СправочникОбъект. Следовательно, использование конструкции без скобок НаборОб.Модифицированность является грубой синтаксической ошибкой.
Почему же конфигуратор не выдал ошибку компиляции? В КД 2.x весь код обработчиков и алгоритмов выполняется динамически через системную функцию Выполнить(). На этапе компиляции модулей этот код не проверяется. При выполнении в режиме предприятия обращение к методу без круглых скобок возвращает тип Метод (или Неопределено в зависимости от релиза платформы), что в логическом условии Если... трактуется платформой как Ложь.
Теперь разберем самое интересное: почему добавление строки Сообщить("НаборОб = " + НаборОб) заставляло алгоритм работать?
В этой строке происходит конкатенация (сложение) строкового литерала и переменной НаборОб, которая содержит тип СправочникОбъект. Чтобы выполнить эту операцию, платформа 1С выполняет неявное приведение типов (аналог вызова встроенной функции Строка(НаборОб)).
Для формирования строкового представления объекта (которое обычно состоит из наименования или кода) платформе требуется актуализировать данные объекта. В этот момент происходит обращение к транзакционному кэшу или непосредственно к СУБД для получения представления объекта.
Этот процесс принудительного обращения к данным «встряхивает» объект в памяти сессии сервера, обновляя его состояние и внутренние структуры метаданных. В результате этого побочного эффекта обращение к ошибочно написанному свойству НаборОб.Модифицированность (без скобок) в конкретных версиях платформы начинало возвращать значение Истина. Как только мы убираем неявное обращение к объекту через Сообщить(), внутреннее состояние кэша не обновляется принудительно, и ошибочный код предсказуемо возвращает Ложь.
Даже если бы мы исправили синтаксическую ошибку и написали код правильно: Если НаборОб.Модифицированность() Тогда, использовать этот метод в серверных алгоритмах обмена крайне не рекомендуется по следующим причинам:
Модифицированность() взводится в значение Истина при любом присвоении значения реквизиту объекта в памяти, даже если новое значение полностью совпадает со старым. Это приводит к лишним перезаписям объектов в базу данных, создавая колоссальную нагрузку на СУБД при переносе десятков тысяч элементов.В исходном коде автора присутствует еще одна критическая архитектурная ошибка:
Объект = Параметры.ПараметрыАлгоритмов.Объект;
В правилах конвертации данных КД 2.x переменная Объект является зарезервированной глобальной переменной контекста. Она указывает на тот объект, который загружается в текущий момент. Переопределяя её внутри общего алгоритма, мы ломаем контекст выполнения выгрузки/загрузки. Это может привести к хаотичным ошибкам записи в других ПКО, потере ссылок и повреждению структуры данных приемника.
Для устранения всех вышеописанных проблем мы должны полностью отказаться от использования платформенного метода Модифицированность() и заменить его явным локальным флагом изменений (переменной типа Булево). Также необходимо корректно передавать параметры в алгоритм, не переопределяя системные переменные КД 2.
Проанализируем правильный вариант реализации алгоритма:
1. Вызов алгоритма в обработчике ПКО (например, ПослеЗагрузки):
// Очищаем параметры алгоритмов перед вызовом
Параметры.ПараметрыАлгоритмов.Очистить();
// Передаем объект через безопасное имя ключа структуры
Параметры.ПараметрыАлгоритмов.Вставить("ТекущийНаборСвойств", Объект);
// Запускаем алгоритм
Выполнить(Алгоритмы.ЗаписатьДополнительныйРеквизитВНабор);
2. Текст самого алгоритма (ЗаписатьДополнительныйРеквизитВНабор):
// Извлекаем переданный объект из безопасного параметра
КонтекстныйОбъект = Параметры.ПараметрыАлгоритмов.ТекущийНаборСвойств;
// Получаем объект набора свойств для редактирования
НаборОб = КонтекстныйОбъект.НаборСвойств.ПолучитьОбъект();
Если НаборОб <> Неопределено Тогда
// Инициализируем флаг необходимости записи
НужноЗаписать = Ложь;
// Проверяем наличие реквизита в табличной части
СтрокаПоиска = НаборОб.ДополнительныеРеквизиты.Найти(Ссылка, "Свойство");
Если СтрокаПоиска = Неопределено Тогда
// Добавляем реквизит, так как его еще нет в наборе
НоваяСтрока = НаборОб.ДополнительныеРеквизиты.Добавить();
НоваяСтрока.Sвойство = Ссылка;
// Взводим наш собственный флаг модификации
НужноЗаписать = Истина;
КонецЕсли;
// Записываем объект только при реальном изменении данных
Если НужноЗаписать Тогда
НаборОб.Записать();
КонецЕсли;
КонецЕсли;
Мы успешно устранили мистическую ошибку и оптимизировали алгоритм обмена. Подводя итог нашей совместной работе, выделим ключевые правила разработки надежных серверных алгоритмов в 1С:
НужноЗаписать = Истина). Это убережет вас от платформенных коллизий кэширования и оптимизирует скорость выполнения обмена.Модифицированность() объекта базы данных требует скобок, в отличие от одноименного свойства управляемой формы.Объект внутри алгоритмов. Для передачи параметров всегда используйте структуру Параметры.ПараметрыАлгоритмов с уникальными ключами.Сообщить() для объектов целиком — это не только плохой тон, но и причина существенного замедления транзакций из-за постоянного неявного обращения к базе данных для формирования строкового представления.