2015-10-21 2 views
2

У меня есть функция, которая сохраняет все записи из таблицы SQL, чтобы листать Excel с помощью EPPlus. Если я экспортирую небольшое количество данных, все работает нормально, но с 200 + столбцами и 500 000+ строк я получаю исключение OutOfMemory.EPPlus сохраняет два миллиона строк с 200 + столбцами, которые могут быть привязаны к нескольким файлам excel.

Я хотел бы изменить свой код, чтобы иметь возможность сохранять 50 000 записей на файл.

Вот мой код, который работает для небольших данных:

private Task SaveAsync(string tableName) 
{ 

    return Task.Run(() => 
    { 
     try 
     { 
      using (var conn = new SqlConnection(_connectionString)) 
      { 
       using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn)) 
       { 
        cmd.CommandType = CommandType.Text; 
        cmd.CommandTimeout = 360; 
        conn.Open(); 
        using (SqlDataReader sdr = cmd.ExecuteReader()) 
        { 
         var fileName = string.Format(TargetFile, tableName); 
         if (File.Exists(fileName)) 
         { 
          File.Delete(fileName); 
         } 

         sdr.Read(); 
         var numberOfRecordsInTable = sdr.GetInt32(0); 

         sdr.NextResult(); 

         using (ExcelPackage pck = new ExcelPackage(new FileInfo(fileName))) 
         { 
          ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Results"); 

          int count = sdr.FieldCount; 
          int col = 1, row = 1; 

          for (int i = 0; i < count; i++) 
          { 
           ws.SetValue(row, col++, sdr.GetName(i)); 
          } 
          row++; 
          col = 1; 
          while (sdr.Read()) 
          { 
           for (int i = 0; i < count; i++) 
           { 
            var val = sdr.GetValue(i); 
            ws.SetValue(row, col++, val); 
           } 
           row++; 
           col = 1; 
          } 
          //autosize 
          ws.Cells[ws.Dimension.Address].AutoFitColumns(); 
          //autofiltr 
          ws.Cells[1, 1, 1, count].AutoFilter = true; 
         } 
        } 
        conn.Close(); 
       } 
      } 
     } 
     catch (Exception e) 
     { 
      Debug.WriteLine("Error at: " + Thread.CurrentThread.ManagedThreadId); 
      Debug.WriteLine(e); 
     } 
    }); 
} 

и мой модифицированный код, который расщепляет записи 50 000 на файл:

private Task SaveAsync2(string tableName) 
{ 
    return Task.Run(() => 
    { 
     try 
     { 
      using (var conn = new SqlConnection(_connectionString)) 
      { 
       using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn)) 
       { 
        cmd.CommandType = CommandType.Text; 
        cmd.CommandTimeout = 360; 
        conn.Open(); 
        using (SqlDataReader sdr = cmd.ExecuteReader()) 
        { 

         var fileName = string.Format(TargetFile, tableName,""); 
         if (File.Exists(fileName)) 
         { 
          File.Delete(fileName); 
         } 

         sdr.Read(); 
         var max = sdr.GetInt32(0); 
         int filesCount = 1; 
         if (max > 50000) 
         { 
          fileName = string.Format(TargetFile, tableName, filesCount); 
         } 

         sdr.NextResult(); 

         ExcelPackage pck = new ExcelPackage(new FileInfo(fileName)); 
         ExcelWorksheet ws = pck.Workbook.Worksheets.Add("RESULTS"); 

         int count = sdr.FieldCount; 

         int col = 1, row = 1; 

         for (int i = 0; i < count; i++) 
         { 
          ws.SetValue(row, col++, sdr.GetName(i)); 
         } 
         row++; 
         col = 1; 
         while (sdr.Read()) 
         { 
          for (int i = 0; i < count; i++) 
          { 
           var val = sdr.GetValue(i); 
           ws.SetValue(row, col++, val); 
          } 
          row++; 
          col = 1; 

          if (row > 50000) 
          { 
           pck.Save(); 
           filesCount++; 
           fileName = string.Format(TargetFile, tableName, filesCount); 

           pck = new ExcelPackage(new FileInfo(fileName)); 
           ws = pck.Workbook.Worksheets.Add("RESULTS"); 

           count = sdr.FieldCount; 

           col = 1; 
           row = 1; 

           for (int i = 0; i < count; i++) 
           { 
            ws.SetValue(row, col++, sdr.GetName(i)); 
           } 
           row++; 
           col = 1; 
          } 
         } 

         //autosize 
         ws.Cells[ws.Dimension.Address].AutoFitColumns(); 
         //autofiltr 
         ws.Cells[1, 1, 1, count].AutoFilter = true; 

         pck.Save(); 
        } 
       } 
       conn.Close(); 

      } 
     } 
     catch (Exception e) 
     { 
      Debug.WriteLine("Error at: " + Thread.CurrentThread.ManagedThreadId); 
      Debug.WriteLine(e); 
     } 
    }); 
} 

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

  1. Как я могу исправить свой код, чтобы удалить дубликат кода и поместить все внутри.
  2. Могу ли я добавить следующий набор (50 000 записей) в качестве нового рабочего листа вместо создания нового файла?
  3. Что было бы ограничением EPPlus при сохранении данных в файл? rows x columns? Я нашел информацию о том, что EPPlus должен обрабатывать более миллиона строк, но не столько столбцов, сколько у меня. Я думаю, что я могу экспортировать миллионы строк с одним столбцом, но для 200 + столбцов для меня 50 000 строк - это ограничение. Мне интересно, есть ли число (строки x столбцов), которые будут ограничением, в котором мой экспорт будет работать нормально. Я хочу, чтобы эта функция экспорта была универсальной, поэтому, когда я передаю данные с 50 столбцами, она будет экспортировать, например, 100 000 строк на файл, а для 2 столбцов она будет экспортировать полмиллиона файлов.

ответ

2

К сожалению, нет простого способа объединить столько данных с Epplus в одном файле. В принципе, весь файл загружается в память при открытии - либо все, либо ничего. Теоретически вы можете генерировать XML-файлы, которые XLSX содержит (они переименованы в zip-файлы), и вручную вставлять их, поскольку он будет иметь меньший размер памяти, но это не маленький подвиг.

Для вашего текущего кода вы всегда можете просто позвонить .dispose() вручную, если хотите избежать использования оператора. Но я понимаю, что вы хотите избежать дублирования кода. А что-то вроде этого (но следить за использование памяти при копировании всех данных объекта):

const int max = 10; 
var loop = 0; 

using (var sdr = cmd.ExecuteReader()) 
{ 
    var fieldcount = sdr.FieldCount; 

    var getfi = new Func<int, FileInfo>(i => 
    { 
     var fi = new FileInfo(String.Format(@"c:\temp\Multi_Files{0}.xlsx", i)); 
     if (fi.Exists) fi.Delete(); 
     return fi; 
    }); 

    var savefile = new Action<FileInfo, List<Object[]>>((info, rows) => 
    { 
     using (var pck = new ExcelPackage(info)) 
     { 
      var wb = pck.Workbook; 
      var ws = wb.Worksheets.Add("RESULTS"); 
      for (var row = 0; row < rows.Count; row++) 
       for (var col = 0; col < fieldcount; col++) 
        ws.SetValue(row + 1, col + 1, rows[row][col]); 
      pck.Save(); 
     } 
    }); 

    var rowlist = new List<Object[]>(); 

    while (sdr.Read()) 
    { 
     var rowdata = new Object[sdr.FieldCount]; 
     sdr.GetValues(rowdata); 
     rowlist.Add(rowdata); 

     if (rowlist.Count == max) 
     { 
      savefile(getfi(++loop), rowlist); 
      rowlist.Clear(); 
     } 
    } 
    if (rowlist.Count > 0) 
     savefile(getfi(++loop), rowlist); 
} 
+0

Благодарим вас за это. Я не знал, что могу использовать «Func» и «Action» таким образом. Я попытаюсь изменить свой код, используя ваш ответ. Прямо сейчас Создаем временный список для хранения данных, которые передаются действию 'savefile'. Могут ли выполняться такие же функции без дополнительных временных переменных? – Misiu

+0

Что будет пределом EPPlus? 'row x columns'? Я нашел информацию о том, что EPPlus должен обрабатывать более миллиона строк, но не столько столбцов. Я думаю, что я могу экспортировать миллионы строк с одним столбцом, но для 107 столбцов для меня 50 000 строк - это ограничение. Мне интересно, есть ли число (строки x столбцов), которые будут ограничением, в котором мой экспорт будет работать нормально. Я хочу, чтобы эта функция экспорта была универсальной, поэтому, когда я передаю данные с 50 столбцами, она будет экспортировать, например, 100 000 строк на файл, а для 2 столбцов она будет экспортировать полмиллиона файлов. – Misiu

+0

@Misiu Да, его временные переменные, которые являются недостатком этого подхода, но поскольку вы используете последовательный читатель, его трудно обойти без какого-либо повторяющегося кода. Что касается фактического предела, то его трудно сказать точно, поскольку он является функцией не только r x c, но и содержимого, то есть строк и чисел. Даже строки являются вариантами, поскольку Excel использует пул строк, поэтому уникальность строк также влияет на него. Я обычно попадаю в стену около 60 тыс. Строк с 150 колонками. – Ernie

0

Поскольку вы создаете новый Excel файл (поправьте меня, если я ошибаюсь), вы можете просто написать XML файл с определенным содержимым. Excel поддерживает файлы .xml, если они содержат правильный материал.

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

Конечно, вам нужно выяснить вручную, что вам нужно записать в .XML-файле. Вы собираетесь использовать форматирование и формулы, это может быть сложным.

Смотрите здесь:

+0

Спасибо за ссылки, но я могу жить с ограничениями EPPlus, поэтому я вырезаю целые данные в пару файлов.Я добавляю форматирование и другие материалы, поэтому я бы хотел избежать этого вручную. – Misiu

0

простое решение без каких-либо уловок (не проверял, но намерения должны быть ясно)

using (var conn = new SqlConnection(_connectionString)) 
{ 
    int filesCount = 1; 
    int col = 1, row = 1; 
    string fileName = String.Empty; 
    int count; 
    ExcelPackage pck; 
    ExcelWorksheet ws; 

    using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn)) 
    { 
     cmd.CommandType = CommandType.Text; 
     cmd.CommandTimeout = 360; 
     conn.Open(); 
     using (SqlDataReader sdr = cmd.ExecuteReader()) 
     { 
       while (sdr.Read()) 
       { 
        if (row == 1) 
        { 
         fileName = string.Format(TargetFile, tableName, filesCount); 
         if (File.Exists(fileName)) 
         { 
          File.Delete(fileName); 
         } 
         pck = new ExcelPackage(new FileInfo(fileName)); 
         ws = pck.Workbook.Worksheets.Add("RESULTS"); 
        } 

        count = sdr.FieldCount; 
        for (int i = 0; i < count; i++) 
        { 
         var val = sdr.GetValue(i); 
         ws.SetValue(row, col++, val); 
        } 
        row++; 
        col = 1; 

        if (row >= 50000) 
        { 
         ws.Cells[ws.Dimension.Address].AutoFitColumns(); 
         ws.Cells[1, 1, 1, count].AutoFilter = true; 
         pck.Save(); 
         row = 1; 
         filesCount+ 
        } 
       } 
      } 
      if (row > 1) 
      { 
       ws.Cells[ws.Dimension.Address].AutoFitColumns(); 
       ws.Cells[1, 1, 1, count].AutoFilter = true; 
       pck.Save(); 
      } 
    } 
} 
conn.Close(); 
+0

Я сделал что-то очень похожее, но, к сожалению, я получаю «OutOfMemoryException» после сохранения 7-й части (когда EPPlus пытается сохранить 8-ю часть). Я могу добавить всю ошибку в свой вопрос, что бы помочь – Misiu

+0

Вы можете попробовать pck.Dispose() после команды save. – nabuchodonossor

+0

Я уже пробовал это. Я избавляюсь от pck и ws. Я установил ограничение на файл на 300000 сначала, тогда у меня есть исключения, теперь я пытаюсь получить количество строк в файле, которые будут работать без исключения исключения. После полировки все, что я, вероятно, поставил бы вопрос о codereview. В идеале я бы поставил миллионы строк на файл, но EPPlus не справится с этим, особенно с более 100 столбцами. – Misiu

7

Я столкнулся с ограничениями памяти с EPPlus в прошлом и в итоге создал несколько файлов .xlsx в качестве обходного пути (аналогично вашему подходу). Другой альтернативой было бы изменить настройки вашего компилятора на целевые только 64-битные (если вы можете обойтись без поддержки 32-разрядных платформ). Насколько я помню, EPPlus скомпилирован для «Any CPU», поэтому, если вы можете изменить свой код на целевой «x64», который, скорее всего, ослабнет ограничениями памяти и позволит вам сгенерировать один файл .xlsx. Ориентация x64, вероятно, сработала бы в моем случае, но я не думал об этом до тех пор, пока это не произошло, поэтому у меня не было возможности проверить.

ОБНОВЛЕНИЕ: Я только что проверил быструю проверку, используя EPPlus 3.1.3, создав 500 000 строк по 70 столбцов. Мое 32-битное приложение могло генерировать около 119 000 строк, прежде чем генерировать исключение из памяти. После переключения цели на x64 она успешно сгенерировала все 500 000 строк, хотя это потребовалось навсегда. Создание фактического рабочего листа заняло всего несколько минут, но ExcelPackage.SaveAs() заняло около 20 минут. Потребление RAM также было довольно высоким (примерно 11 ГБ ОЗУ). Полученный .xlsx - 220 МБ, который 32-разрядный Excel не может открыть (из памяти). Практический результат: Ориентация x64, вероятно, не является жизнеспособным решением; вы бы лучше разделили вывод на несколько файлов .xlsx.

У меня возникло искушение удалить этот ответ, поскольку он оказался тупиком, но решил оставить его на случай, если он поможет кому-то еще избежать этого пути в будущем.

+1

Это может сработать! Я не пытался изменить цель на x64, я попробую сразу! – Misiu

+0

Я попытался изменить целевую платформу на x64, но после этого мое приложение зависает после того, как я нажимаю на экспорт. Изменение целевой платформы было единственным изменением, которое я сделал. – Misiu

+0

@misiu: Может случиться так, что он не зависал, но был очень медленным. См. новую информацию выше. – cbranch

Смежные вопросы