В процессе разработки во внешней обработке или документе часто возникает задача программно обновить данные в регистре сведений. Однако на практике разработчики нередко сталкиваются с ситуацией, когда код выполняется без ошибок, отладчик показывает успешное изменение полей в памяти, но после завершения работы программы данные в регистре сведений остаются прежними. Вызов метода Записать() попросту игнорируется базой данных.
В этой статье мы подробно разберем, почему возникает подобное поведение платформы «1С:Предприятие», проанализируем конфликт перезаписи данных, выясним разницу между использованием объекта РегистрСведенийМенеджерЗаписи и РегистрСведенийНаборЗаписей, а также разберем подводные камни, способные привести к полной потере данных в регистре.
Рассмотрим исходный программный код, из-за которого возникла проблема у автора темы на форуме:
Контрагент = Справочники.Контрагенты.НайтиПоНаименованию("Иванов Иван").ПолучитьОбъект();
НаборЗаписей = РегистрыСведений.КонтрагентыСведенияДляСписка.СоздатьМенеджерЗаписи();
НаборЗаписей.Контрагент = Контрагент.Ссылка;
НаборЗаписей.Прочитать();
Если НаборЗаписей.Выбран() Тогда
НаборЗаписей.Telephone = Контрагент.Телефон;
НаборЗаписей.Записать();
КонецЕсли;
На первый взгляд логика верна: мы находим контрагента, получаем его объектную ссылку, позиционируем менеджер записи на нужной строке регистра сведений по измерению Контрагент, считываем существующую запись, меняем ресурс Телефон и вызываем Записать(). Но почему изменения пропадают при завершении работы обработки?
Выясним причину этой проблемы. В современных типовых конфигурациях 1С (особенно построенных на Библиотеке стандартных подсистем — БСП) многие регистры сведений выполняют вспомогательную роль (например, оптимизация динамических списков, хранение кэша или контактной информации). Название регистра КонтрагентыСведенияДляСписка прямо указывает на то, что это служебный регистр для быстрого вывода информации в форму списка справочника.
В таких архитектурных моделях заполнение регистра происходит автоматически при записи самого объекта-владельца (в нашем случае — справочника Контрагенты). Это реализуется через подписки на события или непосредственно в модуле объекта в обработчиках ПередЗаписью или ПриЗаписи.
Проанализируем цепочку событий, которая привела к ошибке:
Контрагент в память, изменил в нем какие-то данные (или просто удерживал ссылку на объект).МенеджерЗаписи. Изменение успешно зафиксировалось в базе данных на короткий промежуток времени.Контрагент. При перезаписи контрагента срабатывает штатный механизм платформы или БСП, который считывает телефон из реквизитов справочника и принудительно обновляет запись в регистре КонтрагентыСведенияДляСписка.Решение проблемы: В подобных ситуациях изменять данные напрямую в регистре сведений — грубейшая ошибка проектирования. Нам необходимо менять реквизиты у самого объекта-источника и записывать его, позволяя системе автоматически актуализировать зависимые регистры:
КонтрагентОбъект = Справочники.Контрагенты.НайтиПоНаименованию("Иванов Иван").ПолучитьОбъект();
КонтрагентОбъект.Телефон = "+7 (999) 111-22-33";
КонтрагентОбъект.Записать(); // Система сама обновит регистр КонтрагентыСведенияДляСписка
Запись самого контрагента полностью решает проблему актуализации данных.
Обратим внимание на важную дискуссию в сообщениях форума. Разработчики часто спорят о том, что лучше использовать для записи данных: МенеджерЗаписи или НаборЗаписей. Платформа 1С накладывает жесткие ограничения и имеет важные технические нюансы работы с этими объектами.
Встроенная справка 1С (вызываемая по Ctrl+F1) гласит, что объект РегистрСведенийМенеджерЗаписи предназначен в первую очередь для интерактивной работы с записью регистра сведений (например, для связи с полями ввода на управляемой форме). Использование его для массовой программной модификации данных не рекомендуется по нескольким причинам.
Причина 1: Двойной вызов обработчиков событий (Логическая ловушка).
Мало кто из разработчиков задумывается над тем, как платформа обрабатывает вызов метода Записать() у объекта МенеджерЗаписи под капотом. Если в процессе модификации записи были изменены ключевые поля (измерения), платформа 1С выполняет следующие действия:
В результате этого процесса обработчики событий (такие как ПередЗаписью и ПриЗаписи) в модуле набора записей данного регистра сработают дважды! Если внутри этих обработчиков написана тяжелая бизнес-логика, расчеты или проверки, это приведет к серьезному снижению производительности или к логическим ошибкам выполнения.
Причина 2: Низкая производительность (Транзакционный коллапс).
Каждая запись через МенеджерЗаписи инициализирует отдельную транзакцию базы данных. Если вам необходимо обновить 10 000 записей в цикле с использованием менеджера записи, платформа совершит 10 000 транзакций записи. Это парализует работу СУБД и вызовет блокировки таблиц. Использование объекта НаборЗаписей позволяет записать весь массив данных одной транзакцией, что ускоряет работу в десятки и сотни раз.
Рассмотрим, как правильно переписать код обновления регистра сведений через НаборЗаписей:
НаборЗаписей = РегистрыСведений.КонтрагентыСведенияДляСписка.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Контрагент.Установить(КонтрагентСсылка);
НаборЗаписей.Прочитать();
Если НаборЗаписей.Количество() > 0 Тогда
Для каждого Запись из НаборЗаписей Цикл
Запись.Телефон = НовоеЗначениеТелефона;
КонецЦикла;
НаборЗаписей.Записать();
КонецЕсли;
Переходя к использованию наборов записей, начинающие программисты часто совершают одну критическую ошибку, которая приводит к мгновенной потере всех данных в регистре сведений.
Разберем механизм работы метода Записать() у объекта РегистрСведенийНаборЗаписей. По умолчанию этот метод принимает параметр Замещать со значением Истина. Это означает, что платформа при записи набора сначала удаляет из базы данных ВСЕ существующие записи, которые соответствуют установленному в наборе отбору, а затем записывает новые элементы из набора.
Критический сценарий: Если вы создали набор записей, не установили никаких отборов по измерениям и вызвали метод Записать() (или Записать(Истина)), платформа расценит это как команду: "Удалить все записи из регистра сведений, соответствующие пустому отбору (то есть вообще все записи), и записать пустой набор".
В результате выполнения следующего кода весь ваш регистр сведений будет полностью очищен:
// ВНИМАНИЕ: Смертельно опасный код! Очистит весь регистр!
НаборЗаписей = РегистрыСведений.КритическиВажныеСведения.СоздатьНаборЗаписей();
НаборЗаписей.Записать(); // Регистр полностью пуст!
Чтобы обезопасить базу данных от потери информации, возьмем за правило жестко контролировать установку отборов перед вызовом метода записи:
НаборЗаписей = РегистрыСведений.КритическиВажныеСведения.СоздатьНаборЗаписей();
// Установка отбора обязательна!
НаборЗаписей.Отбор.Организация.Установить(ВыбраннаяОрганизация);
НаборЗаписей.Отбор.Период.Установить(ТекущаяДата());
// Заполняем набор новыми данными...
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Организация = ВыбраннаяОрганизация;
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.Значение = 100;
// Запись безопасна только в рамках установленного отбора по Организации и Периоду
НаборЗаписей.Записать(Истина);
Если код написан верно, отборы установлены, транзакция завершается без ошибок, но данные все равно не видны в программе, проанализируем еще несколько скрытых причин:
ОбработкаПроведения или внутри явного программного блока НачатьТранзакцию()), а на более позднем этапе выполнения кода возникло исключение или был вызван метод ОтменитьТранзакцию(), СУБД полностью откатит все изменения. Запись в регистре физически не появится.Отказ = Истина. Если данный вызов в вашей обработке завернут в конструкцию Попытка ... Исключение, то платформа проглотит ошибку записи, и вы даже не узнаете, что запись была заблокирована алгоритмами системы.Элементы.Список.Обновить().Используя приведенные правила и понимая внутреннюю архитектуру работы платформы с СУБД, вы сможете писать надежный, быстрый и безопасный код для работы с регистрами сведений в 1С.