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