Как добавить несколько записей в регистр сведений при создании или изменении одной?

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

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

Понимание объектов: МенеджерЗаписи и НаборЗаписей

Прежде всего, давайте рассмотрим два ключевых объекта для работы с регистрами сведений в 1С:Предприятии — МенеджерЗаписи и НаборЗаписей. Понимание их различий критически важно для корректной реализации нашей задачи.

  1. МенеджерЗаписи (МенеджерЗаписиРегистраСведений)

    Этот объект предназначен для работы с одной уникальной записью регистра сведений. Мы используем его, когда нужно создать, изменить или удалить конкретную запись, однозначно идентифицируемую по ее измерениям. Например, если у нас есть независимый регистр сведений "НастройкиПользователей" с измерениями "Пользователь" и "Настройка", и мы хотим изменить значение одной конкретной настройки для одного пользователя, мы создадим менеджер записи, заполним измерения, ресурсы и вызовем метод Записать(). Важно понимать, что внутри себя менеджер записи также может использовать наборы записей: один для удаления старой записи (если измерения изменились) и один для записи новой. Это удобно для интерактивного редактирования отдельных записей — для этого подойдёт универсальный редактор регистров для ручной корректировки.

  2. НаборЗаписей (НаборЗаписейРегистраСведений)

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

Как показывает опыт, в исходной задаче, скорее всего, использовался МенеджерЗаписи, или подразумевалась запись одной записи, а фактически требовалось создать несколько. Для решения задачи добавления нескольких записей нам однозначно потребуется работать с НаборЗаписей.

Типичная проблема: Ошибка "набор не соответствует установленному отбору"

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

Эта ошибка появляется в нескольких случаях (выявить зависимости между объектами поможет Analyzer 1C):

  1. Не установлен отбор по регистратору (для подчиненных регистров). Если регистр сведений является подчиненным регистратору (например, документу), то при работе с его записями через НаборЗаписей необходимо обязательно установить отбор по этому регистратору. Без этого система не сможет определить, к какому документу относятся записи, и выдаст ошибку. Каждая запись в таком регистре также должна иметь заполненное поле Регистратор, соответствующее установленному отбору.

  2. Записи в наборе не соответствуют условиям отбора. Если мы установили какие-либо условия отбора для НаборЗаписей (например, по определенному измерению), но пытаемся добавить в этот набор записи, которые не удовлетворяют этим условиям, система также выдаст ошибку. Все записи в наборе должны соответствовать заданному отбору.

  3. Некорректная манипуляция с набором в событии ПередЗаписью. Если в обработчике события ПередЗаписью регистра сведений мы пытаемся удалить единственную строку из набора, который собирается записаться, это может привести к тому, что набор станет пустым, но при этом будет ожидаться запись, связанная с изначальными измерениями. В таком сценарии также может возникнуть подобная ошибка, так как система ожидает соответствия.

Подходы к решению: Использование НабораЗаписей для добавления нескольких записей

Для добавления нескольких записей в регистр сведений наиболее надежным и рекомендуемым подходом является работа с объектом НаборЗаписей (подготовить каркас кода поможет консоль кода с ИИ — для этого есть консоль кода и запросов с ИИ). Рассмотрим шаги, которые мы должны предпринять:

  1. Создание объекта НаборЗаписей. Мы начинаем с создания нового экземпляра НаборЗаписей для нужного нам регистра сведений.

    
    // Создаем новый набор записей для регистра 'МойРегистрСведений'
    НаборЗаписейРС = РегистрыСведений.МойРегистрСведений.СоздатьНаборЗаписей();
    
  2. Установка отбора. Если наш регистр сведений подчинен регистратору (например, документу, который мы сейчас проводим), то мы обязательно должны установить отбор по этому регистратору. Это критически важный шаг для подчиненных регистров.

    
    // Для подчиненных регистров сведений обязательно устанавливаем отбор по регистратору
    // Предположим, что 'ДокументОбъект' - это ссылка на объект документа, который записывает регистр
    НаборЗаписейРС.Отбор.Регистратор.Установить(ДокументОбъект.Ссылка);
    

    Если регистр независимый, отбор может быть установлен по другим измерениям, или не установлен вовсе (для программного анализа сложных запросов в таких случаях подойдет процессор схемы запроса).

  3. Добавление новых записей. Теперь мы можем добавлять новые записи в коллекцию НаборЗаписей, используя метод Добавить() (обработать выбранные запросом объекты можно через интерактивный обработчик). Каждая новая запись будет представлять собой объект типа МенеджерЗаписиРегистраСведений (хотя мы работаем с ним в контексте набора).

    
    // Добавляем первую запись
    НоваяЗапись1 = НаборЗаписейРС.Добавить();
    НоваяЗапись1.Измерение1 = "Значение_А";
    НоваяЗапись1.Измерение2 = "Параметр_1";
    НоваяЗапись1.Ресурс1 = 10;
    // Для подчиненных регистров поле Регистратор также должно быть заполнено в каждой записи
    НоваяЗапись1.Регистратор = ДокументОбъект.Ссылка;
    
    // Добавляем вторую запись
    НоваяЗапись2 = НаборЗаписейРС.Добавить();
    НоваяЗапись2.Измерение1 = "Значение_Б";
    НоваяЗапись2.Измерение2 = "Параметр_2";
    НоваяЗапись2.Ресурс1 = 25;
    НоваяЗапись2.Регистратор = ДокументОбъект.Ссылка;
    
  4. Заполнение полей записей. Для каждой добавленной записи мы заполняем ее измерения, ресурсы и реквизиты в соответствии с нашей бизнес-логикой. Помним про обязательность заполнения Регистратор для подчиненных регистров.

  5. Сохранение набора записей. После того как все необходимые записи добавлены в коллекцию НаборЗаписей и их поля заполнены, мы вызываем метод Записать(). Это сохранит все записи из коллекции в базу данных.

    
    Попытка
        НаборЗаписейРС.Записать(); // Записываем весь сформированный набор
    Исключение
        Сообщить("Ошибка при записи регистра сведений 'МойРегистрСведений': " + ОписаниеОшибки());
        // Дополнительная обработка ошибок, если требуется
    КонецПопытки;
    

    Если нам нужно сначала удалить все существующие записи по установленному отбору, а затем записать новые, мы можем использовать методы Прочитать() (для загрузки существующих записей по отбору) и Очистить() (для очистки загруженного набора в памяти, что при последующем Записать() приведет к удалению старых и вставке новых):

    
    // Если нужно сначала удалить все записи по отбору, а затем записать новые
    НаборЗаписейРС.Прочитать(); // Загружаем все существующие записи по установленному отбору
    НаборЗаписейРС.Очистить();  // Очищаем набор в памяти. При Записать() старые записи будут удалены
    
    // ... здесь добавляем новые записи, как показано выше ...
    
    НаборЗаписейРС.Записать(); // Записываем новые записи
    

    Важно: Метод Очистить() очищает только коллекцию записей в объекте НаборЗаписей в памяти. Чтобы изменения отразились в базе данных, необходимо вызвать Записать().

Особенности работы в событиях регистра сведений (ПередЗаписью/ПриЗаписи)

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

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

    Представьте: "Я записываю регистр. Перед записью, нужно записать ещё. Перед записью ещё одной записи, нужно записать ещё...". Это бесконечный цикл.

  2. Ошибка "набор не соответствует установленному отбору". Как мы уже выяснили, при работе с НаборЗаписей требуется строгий отбор. Внутри обработчиков ПередЗаписью или ПриЗаписи мы работаем с набором записей, который уже находится в процессе записи. Попытка добавить в него записи с другими параметрами отбора или создать новый набор и записать его может конфликтовать с текущей транзакцией или привести к вышеупомянутой ошибке.

Рекомендации по работе с событиями:

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

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

  1. Избегайте записи по одной или нескольким записям в цикле. Многократный вызов метода Записать() для отдельных записей или маленьких наборов внутри цикла значительно замедляет работу системы. Каждый вызов Записать() инициирует транзакцию записи в базу данных, что является ресурсоемкой операцией.

  2. Записывайте весь набор целиком. Для оптимальной производительности всегда старайтесь собрать все необходимые записи в один объект НаборЗаписей, а затем вызвать Записать() один раз.

  3. Порционная запись для очень больших объемов. Если количество записей исчисляется десятками и сотнями тысяч, то сбор их всех в один НаборЗаписей может привести к чрезмерному потреблению оперативной памяти. В таких случаях мы можем использовать порционную запись – разбивать весь объем данных на части (например, по 1000 или 5000 записей), формировать НаборЗаписей для каждой порции и записывать ее. После записи каждой порции не забывайте очищать набор методом Очистить() для освобождения памяти и подготовки к следующей порции.

  4. Осторожность при перезаписи. Перезапись большого набора записей, в котором меняется менее 30% данных, часто неоптимальна. Метод Записать() при наличии отбора по умолчанию удаляет все существующие записи по этому отбору, а затем вставляет новые. Это медленнее, чем точечное изменение только измененных данных. Если регистр сведений непериодический и не подчинен регистратору, иногда бывает эффективнее работать через МенеджерЗаписи для точечных изменений, если мы знаем измерения изменяемых записей.

  5. Новые режимы замещения (начиная с 8.3.26). В новых версиях платформы 1С:Предприятие появились новые режимы замещения для объектов НаборЗаписей (РежимЗамещения). Они позволяют более гибко управлять операциями с данными:

    • Слияние: объединяет записи текущего набора с существующими в базе данных.
    • Удаление: удаляет записи из базы данных по установленному отбору.
    • Обновление: обновляет существующие записи и добавляет новые.

    Использование этих режимов может значительно оптимизировать пакетную обработку записей в зависимости от конкретной задачи.

Подчиненные регистры сведений: критические моменты

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

  1. Всегда устанавливайте отбор по регистратору. При создании НаборЗаписей первым делом установите отбор по ссылке на документ-регистратор. Это позволит системе корректно связать записи с документом.

  2. Заполняйте поле Регистратор в каждой записи. Каждая запись, добавляемая в НаборЗаписей подчиненного регистра, должна иметь заполненное поле Регистратор, значение которого должно совпадать с тем, по которому установлен отбор. Иначе вы получите ошибку "набор не соответствует установленному отбору".

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

← На главную