Почему при записи в регистр сведений сохраняется только последняя строка и как это исправить

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке алгоритмов на платформе 1С разработчики нередко сталкиваются с ситуацией, когда код последовательно выполняет запись нескольких значений в регистр сведений, но в результате в базе данных остается только одна, самая последняя строчка. Давайте разберем природу этой ошибки, выясним, как функционирует менеджер записи на уровне платформы, и рассмотрим правильные варианты реализации пакетного сохранения данных.

Проанализируем исходную ситуацию. В коде инициализируется менеджер записи регистра сведений, после чего в цикле или последовательными строками заполняются измерения, ресурсы и многократно вызывается метод записи:


ЗнСв = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();

ЗнСв.Объект = Документ;
ЗнСв.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("ВидПеревозки");
ЗнСв.Значение = ВидПеревозки;
ЗнСв.Записать();

ЗнСв.Объект = Документ;
ЗнСв.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("МаркаАвтомобиля");
ЗнСв.Значение = МаркаАвтомобиля;
ЗнСв.Записать();

Ожидается, что в регистре появятся две строки, однако в таблице сохраняется только МаркаАвтомобиля. Если дописать еще несколько свойств, сохранится лишь самое последнее из них. Выясним причину такого поведения платформы.

Механика работы объекта «Менеджер записи»

Объект РегистрСведенийМенеджерЗаписи предназначен для работы ровно с одной физической записью регистра сведений. Это специализированный инструмент для создания, чтения, модификации или удаления одиночной строки.

Когда мы вызываем метод Записать() в первый раз, платформа фиксирует новую строку в базе данных и связывает данный экземпляр менеджера записи с созданным первичным ключом (набором измерений). При последующем изменении свойств того же самого объекта менеджера и повторном вызове метода Записать() платформа воспринимает это действие не как команду вставить новую строку, а как команду обновить (UPDATE) первичный ключ уже существующей записи. В итоге одна и та же запись последовательно перезаписывается новыми значениями измерений, пока управление не дойдет до последней строки кода.

Рассмотрим, какими способами можно правильно решить задачу добавления нескольких записей.

Способ 1. Создание нового экземпляра менеджера на каждую запись

Если мы хотим остаться в рамках использования менеджера записи, необходимо для каждой сохраняемой строки создавать отдельный экземпляр объекта. Пересоздавая менеджер, мы отвязываем его от предыдущей записи базы данных.

Посмотрим на пример реализации:


// Первая запись
МенеджерЗаписи = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = Документ;
МенеджерЗаписи.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("ВидПеревозки");
МенеджерЗаписи.Значение = ВидПеревозки;
МенеджерЗаписи.Записать();

// Вторая запись — создаем новый менеджер
МенеджерЗаписи = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = Документ;
МенеджерЗаписи.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("МаркаАвтомобиля");
МенеджерЗаписи.Значение = МаркаАвтомобиля;
МенеджерЗаписи.Записать();

Этот способ решает проблему перезаписи, однако при большом объеме данных является неоптимальным: каждый вызов Записать() открывает отдельную неявную транзакцию СУБД, создавая лишнюю нагрузку на сервер баз данных. Если записей много, их следует оборачивать в явную транзакцию с помощью методов НачатьТранзакцию() и ЗафиксироватьТранзакцию(), либо переходить на использование набора записей.

Способ 2. Использование объекта «Набор записей» (Рекомендуемый способ)

Для сохранения нескольких строк, относящихся к одному объекту или периоду, стандартом разработки 1С является объект РегистрСведенийНаборЗаписей. Он позволяет сформировать пакет строк и зафиксировать их в базе данных за одну операцию.

Разберем по шагам, как правильно настроить отбор и записать данные через набор:

  1. Создаем набор записей целевого регистра сведений через метод СоздатьНаборЗаписей().
  2. Устанавливаем отбор по ведущему измерению (в нашем случае по документу), чтобы работать только со строками этого конкретного объекта.
  3. Заполняем коллекцию новыми записями.
  4. Вызываем метод Записать().

Посмотрим на пример кода с использованием набора записей:


НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(Документ);

// Добавляем первое свойство
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Объект = Документ;
НоваяЗапись.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("ВидПеревозки");
НоваяЗапись.Значение = ВидПеревозки;

// Добавляем второе свойство
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Объект = Документ;
НоваяЗапись.Свойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("МаркаАвтомобиля");
НоваяЗапись.Значение = МаркаАвтомобиля;

// Записываем весь набор одной операцией
НаборЗаписей.Записать();

Важное архитектурное предупреждение: по умолчанию метод Записать() у набора записей выполняется с параметром Замещать = Истина. Это означает, что при установленном отборе по документу платформа полностью сотрет все ранее сохраненные свойства этого документа в регистре и запишет только те, которые вы добавили в текущий набор. Если вам нужно точечно изменить или дополнить лишь часть свойств без удаления старых, перед заполнением набора обязательно выполните вызов НаборЗаписей.Прочитать(), найдите нужные строки или добавьте недостающие, и только после этого вызывайте Записать().

Способ 3. Универсальное заполнение через структуру и цикл

Чтобы избежать дублирования строк кода при заполнении большого списка свойств, алгоритм можно автоматизировать с помощью коллекции Структура или ТаблицаЗначений. Это сделает код компактным и удобным для чтения.

Разберем пример пакетной подготовки данных и их записи в цикле:


// Формируем соответствие имен свойств и значений
СвойстваДокумента = Новый Структура;
СвойстваДокумента.Вставить("ВидПеревозки", ВидПеревозки);
СвойстваДокумента.Вставить("МаркаАвтомобиля", МаркаАвтомобиля);
СвойстваДокумента.Вставить("ГосНомерАвтомобиля", ГосНомерАвтомобиля);
СвойстваДокумента.Вставить("СрокДоставки", СрокДоставки);

НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Объект.Установить(Документ);

Для Каждого КлючИЗначение Из СвойстваДокумента Цикл
    СсылкаНаСвойство = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию(КлючИЗначение.Ключ);
    
    Если ЗначениеЗаполнено(СсылкаНаСвойство) Тогда
        СтрокаЗаписи = НаборЗаписей.Добавить();
        СтрокаЗаписи.Объект = Документ;
        СтрокаЗаписи.Свойство = СсылкаНаСвойство;
        СтрокаЗаписи.Значение = КлючИЗначение.Значение;
    КонецЕсли;
КонецЦикла;

НаборЗаписей.Записать();

Рекомендации по оптимизации производительности

Проанализируем две важные детали, влияющие на быстродействие и надежность рассмотренного функционала:

  1. Поиск по наименованию в цикле: Вызов НайтиПоНаименованию на каждой итерации формирует отдельный запрос к базе данных. Если перечень свойств фиксирован, надежнее сделать их предопределенными элементами плана видов характеристик и обращаться к ним напрямую через точку: ПланыВидовХарактеристик.СвойстваОбъектов.ВидПеревозки. Если свойства создаются пользователями динамически, эффективнее один раз отобрать их общим запросом по массиву наименований.
  2. Использование стандартов БСП: В типовых конфигурациях прямая запись в регистр ЗначенияСвойствОбъектов часто нежелательна, поскольку дополнительные реквизиты могут дублироваться в табличной части объекта или требовать пересчета служебных кэшей. В таких системах рекомендуется использовать стандартный программный интерфейс общего модуля, например процедуру УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(...).
← На главную