Работа с большими файлами экселя

Источник: habrahabr

Что такое большой файл? Ну так чтобы реально большой? В бытность свою я думал, что это файлик на 50-60 тыс строк записей. И оставался я бы в таком неведении до сих пор, но пришлось выполнять один проект, в котором надо было работать с файлами на 600-800 тыс строк.

Что сначала

 А сначала, друзья мои, ринулись мы в самое простое, что можно придумать. Interop.Excell, и все дела. Казалось. Ага, щаз. Как показали тестовые испытания, данный способ открытия приводил к тому, что за час было прочитано 200 тыс строк экселя, приложение активно потребляло оперативку, и раздвигало плечами остальные процессы на машине. Кончилось все ожидаемо, но следственный эксперимент надо было довести до конца - на 260 тысячах приложение свалилось в OutOfMemory на машине с 4 Гб. Стало понятно, что в лоб решить проблему не получится

Google it

 Сколько нам открытий чудных… Гугль привел, как ни странно, в msdn, где я познакомился с двумя методами открытия очень больших файлов: DOM и SAX. Уж за давностью времен не вспомню, но какой то из них отвалился по причине опостылевшей уже на тот момент OutOfMemory, а второй был совершенно неюзабелен в плане доступа к данным. Почему - читаем ниже.

Из чего же, из чего же

 Сделаны наши эксельки. Ни для кого, кто решил копнуть формат чуть глубже, не станет секретом, что в отличие от бинарным xls, xlsx - по сути zip архив с данными. Достаточно поменять расширение ручками и распаковать архив в папку - и мы получим всю внутреннюю структуру документа, что есть не что иное, как набор xml файлов и сопутствующей информации. Как оказалось, в корневом xml нет текстовых данных. Вместо этого мы имеем набор индексов, которые ссылаются на вспомогательный файл, в котором представлены пары "ключ/значение" Одним из вышеприведенных способов открыть то файл можно, но при этом нужно копаться в сопутствующих файлах и вытаскивать из них текстовые значения. Мрак.

И отступила тьма

 После долгих мытарств и стенаний родилось следующее:

 Наши любимые юзинги, которые некоторые личности забывают указывать:
using System;

using System.Collections.Generic;

using System.Data;

using System.Data.OleDb;

using System.IO;

using System.Linq;

using DocumentFormat.OpenXml;

using DocumentFormat.OpenXml.Packaging;

using DocumentFormat.OpenXml.Spreadsheet;

* This source code was highlighted with Source Code Highlighter.

 Собственно, сам код:
 public delegate void MessageHave(string message);

    public delegate void _DataLoaded(List<string> data);

    public delegate void _NewProcent(int col);

    public static _DataLoaded DataLoaded;

    public static _NewProcent NewProcent;

    public static MessageHave MessageHave_Event;

    public static void ReadData(object data)

    {

      //Приводим объект с переданной парой "имя файла"-"выбранный лист экселя"

      var keyValuePair = (KeyValuePair<string, string>)data;

      using (var cnn = new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" +

                         keyValuePair.Key + @";Extended Properties=""Excel 12.0;HDR=No;IMEX=1""")

        )

      {

        int calc = 1000;

        MessageHave_Event("Открытие соединения провайдера");

        cnn.Open();

        try

        {

          var cmd = new OleDbCommand(String.Format("select * from [{0}]", keyValuePair.Value), cnn);

          using (OleDbDataReader dr = cmd.ExecuteReader())

          {

            var lines = new List<string>();

            int id = 0;

            if (dr != null)

              while (dr.Read())

              {

                string text = "";

                for (int i = 0; i < dr.FieldCount; ++i)

                {

                  if (dr[i] != null)

                    text += dr[i] + "^";//добавляем разделитель между ячейками

                  else

                    text += "^";

                }

                lines.Add(text);

                id++;

                if (id == calc)

                {

                  NewProcent(id);

                  calc += 1000;

                }

              }

            DataLoaded(lines);

          }

          cnn.Close();

        }

        catch (Exception ex)

        {

          MessageHave_Event("Exception: " + ex.Message);

          cnn.Close();

        }

      }

    }

* This source code was highlighted with Source Code Highlighter.

 Код показал производительность порядка 15-20 минут на файлах в 600-800 тыс строк записей.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=28471