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