При разработке механизмов загрузки данных (например, остатков товаров из внешних файлов) программисты 1С часто сталкиваются с ситуацией, когда данные по одному складу загружаются корректно, но при попытке добавить данные по второму — информация о первом бесследно исчезает. Или же система выдает фатальную ошибку о дублировании ключевых полей. В этой статье мы подробно разберем, как работает объект НаборЗаписей, почему важно использовать фильтры и как правильно организовать архитектуру загрузки данных из нескольких источников.
Рассмотрим ситуацию, описанную в теме: автор загружает остатки по трем разным складам (контрагентам) из трех разных XML-файлов. Однако в итоге в регистре остаются данные только из последнего обработанного файла. Проанализируем базовый механизм работы метода НаборЗаписей.Записать(). В 1С этот метод работает по следующему алгоритму:
Отбор.НаборЗаписей.Если программист не установил фильтр по конкретному измерению (например, по складу), то при вызове метода Записать() система считает, что отбор пустой. А пустой отбор означает "все записи регистра". Таким образом, при загрузке второго файла система сначала удаляет вообще всё, что было в регистре (включая данные первого файла), а затем записывает только данные текущего файла. Именно поэтому в итоге остается только информация из последнего источника.
Чтобы избежать полной очистки регистра, мы должны четко указать системе, какую именно область данных мы хотим обновить. Разберем по шагам, как модифицировать процедуру записи. Перед тем как добавлять новые строки в набор, необходимо установить фильтр по ключевому измерению.
Рассмотрим пример правильного кода для функции записи:
Функция ЗаписатьКоличествоВРегистр(ТЗ, Контрагент)
// Создаем объект набора записей
НаборЗаписей = РегистрыСведений.НаличиеНаСкладеКонтрагента.СоздатьНаборЗаписей();
// КРИТИЧЕСКИ ВАЖНО: Устанавливаем фильтр по контрагенту.
// Теперь при вызове Записать() удалятся данные только по ЭТОМУ контрагенту.
НаборЗаписей.Отбор.Контрагент.Установить(Контрагент);
Для Каждого Строка Из ТЗ Цикл
Если НЕ Строка.Номенклатура.Пустая() Тогда
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Контрагент = Контрагент;
НоваяЗапись.Номенклатура = Строка.Номенклатура;
НоваяЗапись.Количество = Строка.Остаток;
// Логика заполнения ресурсов
НоваяЗапись.ЕстьНаСкладе = ?(НоваяЗапись.Количество > 0, Истина, Ложь);
КонецЕсли;
КонецЦикла;
// Записываем данные. Старые записи по этому контрагенту заменятся новыми.
НаборЗаписей.Записать();
КонецФункции
Обратите внимание: если вы хотите не заменять старые данные, а дополнять их, вам потребуется метод НаборЗаписей.Прочитать(). Если вызвать его после установки отбора, набор заполнится текущими данными из базы, и вы сможете добавить к ним новые строки методом Добавить().
Проанализируем ситуацию, когда при выполнении метода Записать() возникает ошибка уникальности. Это происходит, если в рамках одного набора записей вы пытаетесь записать две или более строки, у которых набор измерений абсолютно идентичен. В нашем случае измерениями являются Контрагент и Номенклатура.
Если в файле XML одна и та же номенклатура встречается дважды (например, в разных секциях документа), то при переборе таблицы значений ТЗ в набор будет добавлено две записи с одинаковой парой "Контрагент + Номенклатура". Регистр сведений не может хранить такие данные по определению.
Для решения этой проблемы выясним причину появления дублей в исходных данных и применим метод Свернуть(). Рассмотрим пример подготовки таблицы значений перед записью:
// Перед циклом записи необходимо объединить дублирующиеся строки
// Суммируем ресурс "Остаток" в разрезе измерения "Номенклатура"
ТЗ.Свернуть("Номенклатура", "Остаток");
// Теперь в ТЗ гарантированно уникальные строки по номенклатуре
ЗаписатьКоличествоВРегистр(ТЗ, Контрагент);
Посмотрим на пример кода из сообщения №4, где загрузка идет в цикле. Код можно сделать значительно лаконичнее и профессиональнее. Вместо того чтобы прописывать условия для каждого счетчика, проанализируем ситуацию: меняется только имя файла и код контрагента. Мы можем использовать конкатенацию строк.
Для Счетчик = 1 По 3 Цикл
НомерСтрокой = Строка(Счетчик);
НаименованиеФайла = "Контрагент" + НомерСтрокой + ".xml";
// Поиск контрагента по коду, который коррелирует с номером файла
КонтрагентСсылка = Справочники.Контрагенты.НайтиПоКоду("Контрагент" + НомерСтрокой);
Если НЕ КонтрагентСсылка.Пустая() Тогда
ПолучениеФайловПоFTP(НаименованиеФайла);
ТаблицаДанных = ПрочитатьФайл(НаименованиеФайла);
ЗаписатьКоличествоВРегистр(ТаблицаДанных, КонтрагентСсылка);
КонецЕсли;
КонецЦикла;
Выясним, как настройки метаданных влияют на работу нашего регистра. В сообщении №12 упоминается важность свойств измерений. Рассмотрим их подробнее:
Контрагент, то запись регистра будет "жить", пока жив объект-владелец. При удалении контрагента из базы все его остатки в этом регистре будут удалены автоматически. Это критически важно для поддержания чистоты базы данных.Записать(), особенно если объем данных в регистре исчисляется миллионами строк.Подведем итог нашему разбору. Для стабильной работы массовой загрузки данных в регистры сведений придерживайтесь следующих правил:
НаборЗаписей, если вы обновляете данные порционно (по складу, по организации, по периоду).НачатьТранзакцию() ... ЗафиксироватьТранзакцию(), вы значительно увеличите скорость записи за счет уменьшения количества обращений к файловой системе сервера БД.Использование этих простых, но эффективных приемов позволит вам создавать надежные механизмы интеграции, которые не будут повреждать уже существующие в системе данные.