При разработке внешних обработок интеграции, загрузки данных или помарочного учета алкоголя в отраслевых решениях (таких как «Трактиръ» от ГК «СофтБаланс») программисты часто сталкиваются с мистическим поведением платформы 1С:Предприятие. Код выполняется без ошибок, транзакция успешно завершается, пустые блоки Попытка...Исключение молчат, но данные в одной информационной базе появляются, а в другой — бесследно исчезают или перезаписывают существующие карточки. В этой статье мы подробно разберем, почему так происходит, проанализируем внутренние механизмы работы с регистрами сведений и научимся правильно использовать объект РегистрСведенийМенеджерЗаписи.
Рассмотрим практическую ситуацию. В нескольких заведениях (барах, ресторанах) установлены однотипные базы данных, развернутые из одного файла конфигурации (.cf). На всех точках запускается одна и та же внешняя обработка, которая должна записывать новые акцизные марки ЕГАИС в регистр сведений АкцизныеМарки. Однако на одной точке процесс проходит успешно, а на другой новые марки не появляются в списке, хотя отладчик показывает, что код записи выполняется и никаких исключений не возникает.
Посмотрим на исходный код, который использовался для записи марок:
ТекРег = РегистрыСведений.АкцизныеМарки.СоздатьМенеджерЗаписи();
ТекРег.PDF417 = Марка.PDF417;
ТекРег.EAN13 = Марка.EAN13;
ТекРег.Номенклатура = Справочники.Номенклатура.НайтиПоКоду(Марка.НоменклатураКод);
ТекРег.ИНН = Марка.ИНН;
ТекРег.КПП = Марка.КПП;
Если ЗначениеЗаполнено(ТекРег.Номенклатура) Тогда
Попытка
ТекРег.Записать();
// Запись вспомогательных данных в таблицу возврата
СтрТабОтв = ТаблВозврата.Добавить();
СтрТабОтв.PDF417 = Марка.PDF417;
// ... заполнение остальных полей ...
Исключение
// Ошибка скрыта, логирование отсутствует
КонецПопытки;
КонецЕсли;
На первый взгляд код выглядит стандартно. Но именно в нем кроется классическая архитектурная ошибка работы с объектами метаданных 1С. Выясним причину, почему этот код работает нестабильно в разных базах данных.
Объект РегистрСведенийМенеджерЗаписи предназначен для работы с одной конкретной записью регистра сведений. Его ключевая особенность заключается в том, что уникальность записи в таблице базы данных определяется исключительно набором ключевых полей — измерений регистра.
Когда мы создаем новый менеджер записи с помощью метода СоздатьМенеджерЗаписи(), платформа выделяет в оперативной памяти пустой объект. Когда мы заполняем его свойства и вызываем метод Записать(), платформа выполняет транзакционную операцию в базе данных:
Важный нюанс: если перед вызовом метода Записать() мы не вызвали метод Прочитать(), менеджер записи ничего «не знает» о текущем состоянии полей (ресурсов и реквизитов) уже существующей записи в базе данных. В результате все ресурсы и реквизиты, которые не были явно заполнены в коде нашей обработки, будут перезаписаны значениями по умолчанию (пустые ссылки, пустые строки, числа со значением 0, значение ЛОЖЬ).
Именно это имел в виду автор темы, когда написал: «Удалял предыдущую запись — последующей». Без предварительного чтения код фактически затирал важную аналитику существующих марок, из-за чего они визуально «исчезали» из интерфейса конфигурации «Трактиръ» (например, из-за сброса статуса активности или очистки привязанной номенклатуры).
Ответ кроется в разном наполнении баз данных на торговых точках:
Чтобы избежать затирания данных и гарантировать корректное обновление ресурсов регистра сведений, необходимо всегда соблюдать простое правило: сначала читаем, потом изменяем и записываем. Разберем этот алгоритм по шагам:
Прочитать(). Этот метод обращается к СУБД, ищет запись по ключевым полям и, если находит, заполняет остальные свойства менеджера (ресурсы и реквизиты) актуальными данными из базы.Выбран().Записать().Посмотрим на пример правильной реализации этого механизма в коде 1С:
ТекРег = РегистрыСведений.АкцизныеМарки.СоздатьМенеджерЗаписи();
// Заполняем ключевые измерения для поиска записи
ТекРег.PDF417 = Марка.PDF417;
ТекРег.EAN13 = Марка.EAN13;
// Пытаемся прочитать существующие данные из базы данных
ТекРег.Прочитать();
// Теперь менеджер содержит актуальные данные из БД (если запись существовала).
// Нам не нужно переживать, что мы сотрем другие важные реквизиты.
ТекРег.Номенклатура = Справочники.Номенклатура.НайтиПоКоду(Марка.НоменклатураКод);
ТекРег.ИНН = Марка.ИНН;
ТекРег.КПП = Марка.КПП;
Если ЗначениеЗаполнено(ТекРег.Номенклатура) Тогда
Попытка
// Записываем обновленный объект без потери старых данных в незадействованных ресурсах
ТекРег.Записать();
// Логируем успешную операцию
СтрТабОтв = ТаблВозврата.Добавить();
СтрТабОтв.PDF417 = Марка.PDF417;
Исключение
// Обязательно выводим ошибку в Журнал Регистрации, если что-то пошло не так!
ЗаписьЖурналаРегистрации("ЗагрузкаМарок",
УровеньЖурналаРегистрации.Ошибка, , ,
"Ошибка записи марки: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЕсли;
Если вы столкнулись с ситуацией, когда данные записываются «в пустоту», воспользуйтесь следующими рекомендациями по отладке:
Попытка...Исключение без логирования — это грубейшая ошибка. Если СУБД блокирует запись из-за нарушения уникальности, блокировок таблиц или работы подписок на события «ПередЗаписью», платформа сгенерирует исключение, которое вы просто не увидите. Всегда используйте метод ЗаписьЖурналаРегистрации() внутри блока Исключение.ТекРег.Записать() нажмите клавишу F11. Это позволит войти внутрь модулей регистра сведений (модуль набора записей, модуль менеджера) или перехватить выполнение в подписках на события. Возможно, в конфигурации «Трактиръ» настроена подписка, которая очищает или подменяет данные в момент записи.Применяя данные правила и понимая разницу работы менеджера записи до и после вызова метода Прочитать(), вы сможете разрабатывать надежные интеграционные механизмы, которые будут предсказуемо работать на любых базах данных и конфигурациях 1С.