При разработке механизмов импорта данных из внешних источников, таких как Microsoft Excel, программисты часто сталкиваются с ситуацией, когда вместо ожидаемого массива данных в регистр сведений попадает только одна — последняя — строка из файла. Это классическая проблема, связанная с пониманием того, как работают механизмы записи в 1С:Предприятие 8. Рассмотрим подробнее, почему это происходит и как доработать код для корректной загрузки всех записей.
Проанализируем ситуацию, представленную автором. В его коде используется объект НаборЗаписей, который создается внутри цикла обхода таблицы значений (ТЗ). Основная проблема заключается в следующей строчке:
Для Каждого СтрокаТаблицы Из ТЗ Цикл
НаборДанных = РегистрыСведений.Проба.СоздатьНаборЗаписей();
Проба = НаборДанных.Добавить();
// ... заполнение полей ...
НаборДанных.Записать();
КонецЦикла;
Выясним причину такого поведения. Метод Записать() у объекта РегистрСведенийНаборЗаписей по умолчанию принимает параметр Истина (замещение). Это означает, что при каждом вызове метода 1С сначала удаляет из регистра все записи, которые соответствуют установленному фильтру (отбору), а затем записывает новые записи из набора. Так как в коде автора отбор не установлен, при каждой итерации цикла 1С пытается «очистить» весь регистр и записать туда одну текущую строку из текущей итерации. В итоге, после завершения цикла, в базе остается только результат последней операции записи.
Самый простой способ исправить ситуацию — передать методу Записать параметр Ложь. Это укажет системе, что записи нужно не замещать, а просто добавлять в регистр. Разберем, как изменится код:
НаборДанных.Записать(Ложь);
Однако стоит учитывать, что если в регистре уже существует запись с такими же измерениями и периодом (для периодического регистра), система выдаст ошибку уникальности. Поэтому данный метод хорош только для «чистой» загрузки или если вы уверены в отсутствии дублей.
Более правильный и производительный с точки зрения работы с базой данных подход — это наполнение набора записей внутри цикла и однократный вызов метода Записать в самом конце. Рассмотрим этот вариант:
НаборДанных = РегистрыСведений.Проба.СоздатьНаборЗаписей();
// Если нужно добавить данные к существующим, не очищая регистр:
// НаборДанных.Прочитать();
Для Каждого СтрокаТаблицы Из ТЗ Цикл
НоваяЗапись = НаборДанных.Добавить();
ЗаполнитьЗначенияСвойств(НоваяЗапись, СтрокаТаблицы);
// Не забываем про Период, если регистр периодический!
НоваяЗапись.Период = ТекущаяДата();
КонецЦикла;
НаборДанных.Записать(); // Записываем всё одним махом
Этот способ значительно быстрее, так как обращение к базе данных (транзакция записи) происходит один раз, а не на каждой строке Excel.
Если специфика вашей задачи требует обработки каждой строки по отдельности (например, вам нужно проверить наличие существующей записи, изменить её или выполнить дополнительные расчеты), лучше использовать объект МенеджерЗаписи. Он предназначен специально для работы с одной записью регистра. Проанализируем пример кода:
Для Каждого СтрокаТаблицы Из ТЗ Цикл
МенеджерЗаписи = РегистрыСведений.Проба.СоздатьМенеджерЗаписи();
// Обязательно заполняем КЛЮЧ записи (Измерения + Период)
МенеджерЗаписи.Партнер = СтрокаТаблицы.Партнер;
МенеджерЗаписи.Мероприятие = СтрокаТаблицы.Мероприятие;
МенеджерЗаписи.Период = СтрокаТаблицы.ДатаЗаписи;
// Пытаемся прочитать существующую запись
МенеджерЗаписи.Прочитать();
// Заполняем ресурсы и реквизиты
МенеджерЗаписи.Код = СтрокаТаблицы.Код;
МенеджерЗаписи.Фамилия = СтрокаТаблицы.Фамилия;
// Записываем (если запись была, она обновится, если нет — создастся)
МенеджерЗаписи.Записать();
КонецЦикла;
Как отметили участники форума, уникальность записи в периодическом регистре определяется комбинацией: Период + Измерения. Если автор загружает данные, в которых измерения (Партнер, Мероприятие) одинаковые, а период не указан, 1С будет записывать их на одну и ту же «пустую» дату (01.01.0001). В результате каждая новая строка будет просто перезаписывать предыдущую.
Важные рекомендации по структуре данных:
ТекущаяДата() или начало текущего дня.НайтиПоНаименованию), иначе запись не будет связана с объектами базы.Рассмотрим также вопрос производительности. Использование COM-объекта Excel.Application и чтение каждой ячейки через Cells(Строка, Колонка).Value — это крайне медленный процесс. Для больших файлов (более 1000 строк) рекомендуется использовать чтение всего диапазона в массив:
// Читаем всё сразу в массив 1С
МассивДанных = Excel.Range(Excel.Cells(3, 1), Excel.Cells(ExcelПоследняяСтрока, ExcelПоследняяКолонка)).Value;
// Теперь перебираем массив, это в десятки раз быстрее
Для Сч = 1 По МассивДанных.GetUpperBound(0) Цикл
Код = СокрЛП(МассивДанных[Сч][1]);
// ... и так далее
КонецЦикла;
Также хорошим тоном считается использование блока Попытка...Исключение и обязательное завершение процесса Excel в блоке КонецПопытки, чтобы избежать «зависания» процессов на сервере.
Суммируя все вышесказанное, составим правильную последовательность действий для импорта:
ТаблицуЗначений, либо в массив.Quit()), чтобы освободить память.НачатьТранзакцию()) для обеспечения целостности данных.МенеджерЗаписи (для точечного обновления) или наполняем НаборЗаписей (для массовой вставки).Период и все измерения регистра.ЗафиксироватьТранзакцию()).Следуя этим правилам, вы гарантируете, что все записи из файла Excel будут корректно перенесены в 1С, а производительность обработки будет максимально возможной.