При разработке алгоритмов на платформе 1С разработчики нередко сталкиваются с ситуацией, когда код последовательно выполняет запись нескольких значений в регистр сведений, но в результате в базе данных остается только одна, самая последняя строчка. Давайте разберем природу этой ошибки, выясним, как функционирует менеджер записи на уровне платформы, и рассмотрим правильные варианты реализации пакетного сохранения данных.
Проанализируем исходную ситуацию. В коде инициализируется менеджер записи регистра сведений, после чего в цикле или последовательными строками заполняются измерения, ресурсы и многократно вызывается метод записи:
ЗнСв = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
ЗнСв.Объект = Документ;
ЗнСв.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("ВидПеревозки");
ЗнСв.Значение = ВидПеревозки;
ЗнСв.Записать();
ЗнСв.Объект = Документ;
ЗнСв.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("МаркаАвтомобиля");
ЗнСв.Значение = МаркаАвтомобиля;
ЗнСв.Записать();
Ожидается, что в регистре появятся две строки, однако в таблице сохраняется только МаркаАвтомобиля. Если дописать еще несколько свойств, сохранится лишь самое последнее из них. Выясним причину такого поведения платформы.
Объект РегистрСведенийМенеджерЗаписи предназначен для работы ровно с одной физической записью регистра сведений. Это специализированный инструмент для создания, чтения, модификации или удаления одиночной строки.
Когда мы вызываем метод Записать() в первый раз, платформа фиксирует новую строку в базе данных и связывает данный экземпляр менеджера записи с созданным первичным ключом (набором измерений). При последующем изменении свойств того же самого объекта менеджера и повторном вызове метода Записать() платформа воспринимает это действие не как команду вставить новую строку, а как команду обновить (UPDATE) первичный ключ уже существующей записи. В итоге одна и та же запись последовательно перезаписывается новыми значениями измерений, пока управление не дойдет до последней строки кода.
Рассмотрим, какими способами можно правильно решить задачу добавления нескольких записей.
Если мы хотим остаться в рамках использования менеджера записи, необходимо для каждой сохраняемой строки создавать отдельный экземпляр объекта. Пересоздавая менеджер, мы отвязываем его от предыдущей записи базы данных.
Посмотрим на пример реализации:
// Первая запись
МенеджерЗаписи = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = Документ;
МенеджерЗаписи.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("ВидПеревозки");
МенеджерЗаписи.Значение = ВидПеревозки;
МенеджерЗаписи.Записать();
// Вторая запись — создаем новый менеджер
МенеджерЗаписи = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = Документ;
МенеджерЗаписи.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("МаркаАвтомобиля");
МенеджерЗаписи.Значение = МаркаАвтомобиля;
МенеджерЗаписи.Записать();
Этот способ решает проблему перезаписи, однако при большом объеме данных является неоптимальным: каждый вызов Записать() открывает отдельную неявную транзакцию СУБД, создавая лишнюю нагрузку на сервер баз данных. Если записей много, их следует оборачивать в явную транзакцию с помощью методов НачатьТранзакцию() и ЗафиксироватьТранзакцию(), либо переходить на использование набора записей.
Для сохранения нескольких строк, относящихся к одному объекту или периоду, стандартом разработки 1С является объект РегистрСведенийНаборЗаписей. Он позволяет сформировать пакет строк и зафиксировать их в базе данных за одну операцию.
Разберем по шагам, как правильно настроить отбор и записать данные через набор:
СоздатьНаборЗаписей().Записать().Посмотрим на пример кода с использованием набора записей:
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(Документ);
// Добавляем первое свойство
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Объект = Документ;
НоваяЗапись.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("ВидПеревозки");
НоваяЗапись.Значение = ВидПеревозки;
// Добавляем второе свойство
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Объект = Документ;
НоваяЗапись.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("МаркаАвтомобиля");
НоваяЗапись.Значение = МаркаАвтомобиля;
// Записываем весь набор одной операцией
НаборЗаписей.Записать();
Важное архитектурное предупреждение: по умолчанию метод Записать() у набора записей выполняется с параметром Замещать = Истина. Это означает, что при установленном отборе по документу платформа полностью сотрет все ранее сохраненные свойства этого документа в регистре и запишет только те, которые вы добавили в текущий набор. Если вам нужно точечно изменить или дополнить лишь часть свойств без удаления старых, перед заполнением набора обязательно выполните вызов НаборЗаписей.Прочитать(), найдите нужные строки или добавьте недостающие, и только после этого вызывайте Записать().
Чтобы избежать дублирования строк кода при заполнении большого списка свойств, алгоритм можно автоматизировать с помощью коллекции Структура или ТаблицаЗначений. Это сделает код компактным и удобным для чтения.
Разберем пример пакетной подготовки данных и их записи в цикле:
// Формируем соответствие имен свойств и значений
СвойстваДокумента = Новый Структура;
СвойстваДокумента.Вставить("ВидПеревозки", ВидПеревозки);
СвойстваДокумента.Вставить("МаркаАвтомобиля", МаркаАвтомобиля);
СвойстваДокумента.Вставить("ГосНомерАвтомобиля", ГосНомерАвтомобиля);
СвойстваДокумента.Вставить("СрокДоставки", СрокДоставки);
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(Документ);
Для Каждого КлючИЗначение Из СвойстваДокумента Цикл
СсылкаНаСвойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию(КлючИЗначение.Ключ);
Если ЗначениеЗаполнено(СсылкаНаСвойство) Тогда
СтрокаЗаписи = НаборЗаписей.Добавить();
СтрокаЗаписи.Объект = Документ;
СтрокаЗаписи.Свойство = СсылкаНаСвойство;
СтрокаЗаписи.Значение = КлючИЗначение.Значение;
КонецЕсли;
КонецЦикла;
НаборЗаписей.Записать();
Проанализируем две важные детали, влияющие на быстродействие и надежность рассмотренного функционала:
НайтиПоНаименованию на каждой итерации формирует отдельный запрос к базе данных. Если перечень свойств фиксирован, надежнее сделать их предопределенными элементами плана видов характеристик и обращаться к ним напрямую через точку: ПланыВидовХарактеристик.СвойстваОбъектов.ВидПеревозки. Если свойства создаются пользователями динамически, эффективнее один раз отобрать их общим запросом по массиву наименований.ЗначенияСвойствОбъектов часто нежелательна, поскольку дополнительные реквизиты могут дублироваться в табличной части объекта или требовать пересчета служебных кэшей. В таких системах рекомендуется использовать стандартный программный интерфейс общего модуля, например процедуру УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(...).