Pull to refresh

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

.NET *
Что такое большой файл? Ну так чтобы реально большой? В бытность свою я думал, что это файлик на 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 тыс строк записей.

Если кому то реализация покажется кривой — сильно не пинать :) Выслушаю все комментарии

Tags:
Hubs:
Total votes 50: ↑35 and ↓15 +20
Views 44K
Comments Comments 43