При разработке в среде 1С:Предприятие часто возникает задача сохранить данные, связанные с определенным объектом (например, справочником или планом видов расчета), в отдельный независимый регистр сведений. Казалось бы, задача тривиальная: создать запись или обновить её, если она уже существует.
Однако, начинающие разработчики часто сталкиваются с ошибкой: "Запись с такими ключевыми полями существует". Это происходит при попытке записать данные, которые конфликтуют с уже имеющимися в базе по уникальности измерений.
В этой статье мы подробно разберем, как правильно работать с объектами НаборЗаписей и МенеджерЗаписи, выясним, почему возникает ошибка дублирования, и рассмотрим корректные алгоритмы обновления данных.
Регистр сведений — это таблица, где уникальность строки определяется совокупностью значений его Измерений. Нельзя добавить в регистр две строки с абсолютно одинаковым набором измерений.
Когда мы пытаемся добавить запись программно, система проверяет, нет ли уже такой комбинации измерений в базе данных. Ошибка возникает в двух основных случаях:
Добавить()), не удалив старую с теми же измерениями.Ложь (не замещать), когда запись уже есть.Давайте рассмотрим два основных способа решения задачи: использование набора записей с отбором и использование менеджера записи.
Объект РегистрСведенийНаборЗаписей предназначен для работы с группой записей. Главная особенность метода Записать() у набора записей заключается в его параметре Замещать.
Набор.Записать(Истина) (по умолчанию) — удаляет все записи в базе, соответствующие установленному Отбору набора, и записывает на их место те, что находятся в памяти набора.Набор.Записать(Ложь) — пытается "дописать" записи к существующим. Именно этот режим чаще всего вызывает ошибки дублей.Рассмотрим правильный алгоритм изменения записи через набор. Наша цель: прочитать запись по конкретному измерению, изменить ресурсы, а если записи нет — создать новую.
Разберем код по шагам:
// 1. Создаем набор записей
НаборЗаписей = РегистрыСведений.Расш_2_Соответствие.СоздатьНаборЗаписей();
// 2. Устанавливаем ОТБОР. Это критически важно!
// Если не установить отбор, при записи(Истина) мы можем очистить весь регистр,
// либо при чтении вытянуть всю таблицу в память.
НаборЗаписей.Отбор.Соответ_Начисления.Установить(Объект.Ссылка);
// 3. Считываем данные из базы по этому отбору
НаборЗаписей.Прочитать();
// 4. Проверяем, есть ли запись
Если НаборЗаписей.Количество() > 0 Тогда
// Запись есть, мы её модифицируем.
// Так как отбор стоял по конкретной ссылке, скорее всего там одна запись.
Для Каждого Запись Из НаборЗаписей Цикл
Запись.ОснованиеОМС = ЭтаФорма.ОснованияНеОблагаемыеПФ;
Запись.ОснованиеСПБ = ЭтаФорма.ОснованияНеОблагаемыеСПБ;
Запись.ОснованиеПФ = ЭтаФорма.ОснованияНеОблагаемыеПФ;
EndDo;
Иначе
// Записи нет, создаем новую
НоваяЗапись = НаборЗаписей.Добавить();
// Обязательно заполняем измерение, по которому делали отбор
НоваяЗапись.Соответ_Начисления = Объект.Ссылка;
// Заполняем ресурсы
НоваяЗапись.ОснованиеОМС = ЭтаФорма.ОснованияНеОблагаемыеПФ;
НоваяЗапись.ОснованиеСПБ = ЭтаФорма.ОснованияНеОблагаемыеСПБ;
НоваяЗапись.ОснованиеПФ = ЭтаФорма.ОснованияНеОблагаемыеПФ;
КонецЕсли;
// 5. Записываем с замещением (Истина - значение по умолчанию)
// Система удалит старую запись (если она была) по нашему отбору
// и запишет новую (или измененную) из набора.
НаборЗаписей.Записать();
Важное замечание про производительность:
В обсуждении проблемы автор использовал метод НаборЗаписей.Прочитать() без предварительной установки отбора. Это работает на пустой или маленькой базе, но является грубой ошибкой. Без отбора 1С считывает таблицу регистра целиком. Если в регистре будет миллион записей, сервер попытается загрузить их все в оперативную память, что приведет к зависанию или падению системы. Всегда устанавливайте Отбор перед чтением!
Если регистр независимый и непериодический (или вы точно знаете период), и вам нужно работать строго с одной строкой, удобно использовать РегистрСведенийМенеджерЗаписи. Он работает похожим на справочники образом: нашли, изменили, записали.
Посмотрим на пример реализации через менеджер:
// 1. Создаем менеджер
МенеджерЗаписи = РегистрыСведений.Расш_2_Соответствие.СоздатьМенеджерЗаписи();
// 2. Устанавливаем значения ключевых полей (Измерений)
МенеджерЗаписи.Соответ_Начисления = Объект.Ссылка;
// 3. Пробуем прочитать запись из базы
МенеджерЗаписи.Прочитать();
// 4. Проверяем, нашлась ли запись
Если МенеджерЗаписи.Выбран() Тогда
// Запись существует, обновляем ресурсы
МенеджерЗаписи.ОснованиеОМС = ЭтаФорма.ОснованияНеОблагаемыеПФ;
МенеджерЗаписи.ОснованиеСПБ = ЭтаФорма.ОснованияНеОблагаемыеСПБ;
Иначе
// Записи нет. Измерения уже установлены в шаге 2.
// Просто заполняем ресурсы. При записи создастся новая строка.
МенеджерЗаписи.ОснованиеОМС = ЭтаФорма.ОснованияНеОблагаемыеПФ;
МенеджерЗаписи.ОснованиеСПБ = ЭтаФорма.ОснованияНеОблагаемыеСПБ;
КонецЕсли;
// 5. Записываем
МенеджерЗаписи.Записать();
Этот код более читаем для операций с одиночными записями. Однако, если вам нужно изменить 100 записей подряд, использование Менеджера Записи в цикле будет работать медленнее, чем один Набор Записей.
Записать(Ложь)В ходе обсуждения часто всплывала попытка использовать конструкцию НаборЗаписей.Записать(Ложь). Давайте разберем, почему это приводило к ошибке.
Параметр Ложь означает режим "Добавление" (не замещать). Логика платформы в этом случае такая:
INSERT в таблицу базы данных.Параметр Истина (режим "Замещение") работает иначе:
Отбор набора записей.DELETE) всех записей, попадающих под этот отбор.INSERT) записей, которые сейчас находятся в коллекции набора.Именно поэтому использование Записать(Истина) вместе с правильно установленным отбором является надежным способом обновления данных: старая версия удаляется, новая записывается.
Еще один нюанс, который стоит упомянуть. Если вы пытаетесь записать данные в регистр при создании нового элемента справочника (или Плана Видов Расчета), убедитесь, что у объекта уже есть Ссылка.
Если код выполняется в событии ПередЗаписью на клиенте или сервере для совершенно нового объекта, ссылка может быть еще пустой или "битой" (в зависимости от момента подписки на событие и получения ссылки нового объекта). Рекомендуется писать в зависимые регистры сведений в событии ПриЗаписи, когда основной объект уже гарантированно записан в БД и имеет валидную Ссылку.
НаборЗаписей с установленным Отбором по ключевым измерениям.Прочитать() после установки отбора, чтобы получить текущее состояние данных.Записать() (или Записать(Истина)) для обновления данных. Это безопасно перезапишет данные по установленному отбору.МенеджерЗаписи.