Загрузка из Excel в управляемом приложении

С появлением управляемого интерфейса стали актуальными вопросы в давно освоенных приемах работы. В частности, загрузка из файла.

Столкнулся очередной раз с задачей древней как мир, кому-то из коллег показалось что в excel задача по загрузке информации в регистр сведений 1С (в частности ЛицевыеСчетаСотрудников) будет выполняться проще, чем в интерфейсе 1С. Но не надо на этом зацикливаться, информация, изложенная ниже, носит более широкий характер. Так вот, ввиду отсутствия MS Office на компьютере с базой пользователь благополучно был с обидой отправлен восвояси, при этом на локальном компьютере у пользователя был установлен офис, и это даже ставилось как аргумент. Позже возникла необходимость загрузки информации в похожий регистр, но задача отличалась тем, что теперь требовалось загрузить из Excel с локальной тачки в клиент-серверную базу. Устав людям доказывать, что не одним экселем сыт 1С, взялся за реализацию несложной задачи и так получилось, что увидел в наискучнейшем рутинном занятии повод для изысканий тут. Итак, как ни странно, подробного разбора полетов не нашел ни здесь, ни на профильных сайтах, есть опытные ответчики на сайтах и тут тоже был замечен из категории черного пояса по "Спасибо, кэп", с советами "в управляемом приложении перенеси все &НаКлиент"  или "Конечно, перед открытием нужно указать путь на сервере, проснулся, типа скачай мою обработку и научись". Так вот эта заметка будет полезна тем, кто обойдется без выслушивания подобного рода высказывателей.

Со времен Толстого, задача действительно разделилась. И у банальной процедуры загрузки появились варианты, где ее выполнять, на клиенте или на сервере.

Первый вариант &НаКлиенте

Подходит для озвученной мною задачи, если у вас файл Эксель это просто несколько колонок с загружаемыми реквизитами, простой структуры, типа колонка — снизу ее значение и никаких логических или сложных арифметических действий или индексирования со значениями в колонках не требуется, тогда будет достаточно следующего кода. Замечу, этот код не зависит от типа базы, файлоая/серверная и требуется наличие экселя только на стороне клиента (это из области "Спасибо, кэп", но будет полезно в определении круга задач для тех, кто читал с начала)

&НаКлиенте
Процедура Загрузка(Команда)

Колонки = Новый Массив;
Колонки.Добавить("Сотрудник");
Колонки.Добавить("Счет");

Попытка
ex = ПолучитьCOMобъект("","Excel.Application");
Исключение
Сообщить("Excel Application не создан!!");
Возврат;
КонецПопытки;

Попытка
ex.workbooks.open(Файл,1);
Исключение
Сообщить("Файл перемещен или удален!");
Возврат;
КонецПопытки;

RCount = ex.ActiveSheet.UsedRange.Rows.Count();
Для j = 2 по RCount цикл

Если СокрЛП(ex.ActiveSheet.Cells(j,1).Value)="" Тогда
Продолжить;
КонецЕсли;

Стр = Новый Массив;
Для i=1 По Колонки.Количество() Цикл
Стр.Добавить(СокрЛП(ex.ActiveSheet.Cells(j,i).Value));
КонецЦикла;

ДобавитьЗаписьЛицевойСчет(Стр, ВыбПроект);
КонецЦикла;

ex.workbooks.Close();
ex.quit();

КонецПроцедуры

Второй вариант &НаСервере.

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

&НаКлиенте
Процедура Загрузка(Команда)

Двоичное = Новый ДвоичныеДанные(Файл);
Адрес = ПоместитьВоВременноеХранилище(Двоичное, ЭтаФорма.УникальныйИдентификатор);

ЗагрузкаНаСервере(Адрес, ВыбПроект);
КонецПроцедуры

&НаСервере
Процедура ЗагрузкаНаСервере(Адрес, Проект)

темп_Путь = КаталогВременныхФайлов()+ "prncss_Megan_"+Формат(ТекущаяДата()-Дата(2012,01,01), "ЧГ=0")+".xls";

темп_файл = ПолучитьИзВременногоХранилища(Адрес);
темп_файл.Записать(темп_Путь);

СписокКолонок = Новый СписокЗначений;
СписокКолонок.Добавить("ФизЛицо");
СписокКолонок.Добавить("НомерЛицевогоСчета");

ТабЛицСчетов = ЗагрузитьЭксель(темп_Путь,СписокКолонок);

Попытка
УдалитьФайлы(темп_Путь);
Исключение
Сообщить(ОписаниеОшибки());
КонецПопытки;

....

КонецПроцедуры

&НаСервере
Функция  ЗагрузитьЭксель(Путь, СписокКолонки)

ТЗФайл = Новый ТаблицаЗначений;

Попытка
ex = ПолучитьCOMобъект("","Excel.Application");
Исключение
Сообщить("Excel Application не создан!!");
Возврат ТЗФайл;
КонецПопытки;

Попытка
ex.workbooks.open(Путь,1);
Исключение
Сообщить("Файл перемещен или удален!");
Возврат ТЗФайл;
КонецПопытки;


Для каждого Зн Из СписокКолонки Цикл
ТЗФайл.Колонки.Добавить(Зн.Значение);
КонецЦикла;

RCount = ex.ActiveSheet.UsedRange.Rows.Count();
CCount = ex.ActiveSheet.UsedRange.Columns.Count();
Для j = 2 по RCount цикл

Новая = ТЗФайл.Добавить();
Для i=1 По СписокКолонки.Количество() Цикл
Новая.Установить(i-1, СокрЛП(ex.ActiveSheet.Cells(j,i).Value));
КонецЦикла;

КонецЦикла;

ex.workbooks.Close();
ex.quit();

Возврат ТЗФайл;
КонецФункции

Временный каталог используется стандартного пользователя USR1CV82. В своем коде я еще применил таймстэмп на всякий случай, во избежание ошибки разделенного доступа к файлу в случае неожиданного завершения в прошлый запуск (почему-то окончание фразы сразу подумалось о другом и стало немного грустно 😉  а потом весело: не только 1С-ники косячат).

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

15 Comments

  1. maxdmt

    Если ваш сервер 64бит а мс офис 32 — то вариант на сервере не взлетит.

    Reply
  2. proal

    Начиная с 8.3.8, можно загружать Excel-файл напрямую в ТабличныйДокумент.

    Правда, эту фичу я опробовал только в 8.3.10 версии, поэтому не могу ручаться за корректность загрузки в предыдущих версиях.

    В моем случае файлы простые (1 страница, никаких формул, голый текст и числа), ошибок загрузки пока не встречал.

    А загружается очень просто:

    ОбъектТабличногоДокумента..Прочитать(ИмяВременногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Текст);

    И никакого офиса на сервере не надо, битность тоже не влияет.

    Reply
  3. alex_bitti

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

    Reply
  4. alex_bitti

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

    Reply
  5. PavelKolobkov

    (2) При загрузке через ТабличныйДокумент некорректно читаются символы с диакритическими знаками.

    Reply
  6. Isonic

    предложенный автором способ, и способы загрузки предложенные в комментах не работоспособны при использовании файлов excel с объемом 10к и более строк!

    Загрузка по времени увеличивается в геометрической прогрессии!

    PS: проверено на файлах с количеством строк более 500к. Мощные серваки просто загнулись

    Правильнее использовать метод «Range»

    Reply
  7. Isonic

    (5) поддерживаю

    Reply
  8. Isonic

    (2) теряется часть значений в ячейке!

    Reply
  9. alex_bitti

    (6) а с чем связана проблема? просто давно не приходилось делать большие загрузки, 6 лет назад в 8.2 (УФ) делал загрузку справочников подобным образом, номенклатура была около 50к строк, долго но все зашло, вроде, деталей не помню. сам эксель плохо работает с большими объемами, xls старый формат на сколько мне не изменяет имеет ограничение в 64к строк,

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

    Reply
  10. Isonic

    (9) построчное и поячеечное чтение очень много времени занимает. Не прикольно когда загрузку приходиться ждать 1-2 дня

    Reply
  11. alex_bitti

    (10) я дописал предыдущий коммент, нет загрузилось тогда все в течении дня рабочего

    Reply
  12. MaxS

    (6) Попробуйте в 1С открыть большой файл через главное меню Файл- Открыть. Если открывается, то и программой не составит труда обработать через ТабличныйДокумент.

    У меня обработкой без проблем читаются 100к строк.

    Reply
  13. alex_bitti

    (12) табличный документ не для всего подходит, например из 115 колонок нужно загрузить 40 не подряд естественно, и несколько сот строк тоже не подряд, структура загружаемых данных заранее не известна, зависит от содержания, простой пример такой ситуации я написал в основном тексте, есть графики с календарями, в некоторых месяцах 30 денй в некоторых 31, у кого то есть ночные часы у кого то их нет

    Reply
  14. MaxS

    (13) 1С с табличным документом быстрее работает, чем с внешней программой, к которой нужно неоднократно обращаться для считывания данных. Запуск сторонней программы наверное больше памяти съест, чем загрузка всего файла в табличный документ.

    Reply
  15. alex_bitti

    (14) мне кажется это немного другая тема, повторюсь, в 1С есть возможность нормально создать объект из модуля, или реквизит объекта из модуля? предложенный вами метод частный случай, но не спорю более производительный, хотя если углубиться дальше 1С можно было бы поспорить

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *