How to write on multiple worksheets using EPPlus - c#

I'm using the following code snippet to write some data into an excel file using EPPlus. My application does some big data processing and since excel has a limit of ~1 million rows, space runs out time to time. So what I am trying to achieve is this, once a System.ArgumentException : row out of range is detected or in other words.. no space is left in the worksheet.. the remainder of the data will be written in the 2nd worksheet in the same workbook. I have tried the following code but no success yet. Any help will be appreciated!
try
{
for (int i = 0; i < data.Count(); i++)
{
var cell1 = ws.Cells[rowIndex, colIndex];
cell1.Value = data[i];
colIndex++;
}
rowIndex++;
}
catch (System.ArgumentException)
{
for (int i = 0; i < data.Count(); i++)
{
var cell2 = ws1.Cells[rowIndex, colIndex];
cell2.Value = data[i];
colIndex++;
}
rowIndex++;
}

You shouldnt use a catch to handle that kind of logic - it is more for a last resort. Better to engineer you code to deal with your situation since this is very predictable.
The excel 2007 format has a hard limit of 1,048,576 rows. With that, you know exactly how many rows you should put before going to a new sheet. From there it is simple for loops and math:
[TestMethod]
public void Big_Row_Count_Test()
{
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
if (existingFile.Exists)
existingFile.Delete();
const int maxExcelRows = 1048576;
using (var package = new ExcelPackage(existingFile))
{
//Assume a data row count
var rowCount = 2000000;
//Determine number of sheets
var sheetCount = (int)Math.Ceiling((double)rowCount/ maxExcelRows);
for (var i = 0; i < sheetCount; i++)
{
var ws = package.Workbook.Worksheets.Add(String.Format("Sheet{0}", i));
var sheetRowLimit = Math.Min((i + 1)*maxExcelRows, rowCount);
//Remember +1 for 1-based excel index
for (var j = i * maxExcelRows + 1; j <= sheetRowLimit; j++)
{
var cell1 = ws.Cells[j - (i*maxExcelRows), 1];
cell1.Value = j;
}
}
package.Save();
}
}

Related

export to excel from c# is not showing first row from database

I am trying to export a database from c# to excel but the first row from the database is not saving in excel.
private void exporttoexcel()
{
Microsoft.Office.Interop.Excel._Application excel = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel._Workbook workbook = excel.Workbooks.Add(Type.Missing);
Microsoft.Office.Interop.Excel._Worksheet worksheet = null;
try
{
worksheet = workbook.ActiveSheet;
worksheet.Name = "ExportedFromDatGrid";
int cellRowIndex = 1;
int cellColumnIndex = 1;
//Loop through each row and read value from each column.
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
for (int j = 0; j < dataGridView1.Columns.Count; j++)
{
// Excel index starts from 1,1. As first Row would have the Column headers, adding a condition check.
if (cellRowIndex == 1)
{
worksheet.Cells[cellRowIndex, cellColumnIndex] = dataGridView1.Columns[j].HeaderText;
}
else
{
worksheet.Cells[cellRowIndex, cellColumnIndex] = dataGridView1.Rows[i].Cells[j].Value.ToString();
}
cellColumnIndex++;
}
cellColumnIndex = 1;
cellRowIndex++;
}
}
catch(Exception ex)
{
}
}
here is the code I'm using. could anyone help me ? I am new in coding.
You're not writing out the data but are only writing out column names when the cellColumnIndex is 1, skipping the first row. But after the first row has been processed, the row index will be incremented. Refactor your for-loop to look something like this:
// Add the column names
var index = 0;
foreach(var column in dataGridView1.Columns)
{
worksheet.Cells[0, index] = column.HeaderText;
index++;
}
//Loop through each row and read value from each column.
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
for (int j = 0; j < dataGridView1.Columns.Count; j++)
{
// Excel index starts from 1,1. As first Row would have the Column headers, adding a condition check.
worksheet.Cells[cellRowIndex, cellColumnIndex] = dataGridView1.Rows[i].Cells[j].Value.ToString();
cellColumnIndex++;
}
cellColumnIndex = 1;
cellRowIndex++;
}
Please have a look at ClosedXML. It simplifies writing your code, and eliminate the need to have Excel installed on the machine where you want to run this.

SSIS Export to Excel using Script Task

I'm trying to use a Script Task to export data to Excel because some of the reports I generate simply have too many columns to keep using a template file.
The most annoying part about using a template is: if something as simple as a column header changes, the metadata gets screwed forcing me to recreate my DataFlow. Because I use an OLE DB source, I need to use a Data Transformation task to convert between unicode and non-unicode character sets, then remap my Excel Destination to the "Copy of field x" in order for the Excel document to create properly.
This takes far too long and I need a new approach.
I have the following method in a script task using Excel = Microsoft.Office.Interop.Excel:
private void ExportToExcel(DataTable dataTable, string excelFilePath = null)
{
Excel.Application excelApp = new Excel.Application();
Excel.Worksheet workSheet = null;
try
{
if (dataTable == null || dataTable.Columns.Count == 0)
throw new System.Exception("Null or empty input table!" + Environment.NewLine);
excelApp.Workbooks.Add();
workSheet = excelApp.ActiveSheet;
for (int i = 0; i < dataTable.Columns.Count; i++)
{
workSheet.Cells[1, (i + 1)] = dataTable.Columns[i].ColumnName;
}
foreach (DataTable dt in dataSet.Tables)
{
// Copy the DataTable to an object array
object[,] rawData = new object[dt.Rows.Count + 1, dt.Columns.Count];
// Copy the column names to the first row of the object array
for (int col = 0; col < dt.Columns.Count; col++)
{
rawData[0, col] = dt.Columns[col].ColumnName;
}
// Copy the values to the object array
for (int col = 0; col < dt.Columns.Count; col++)
{
for (int row = 0; row < dt.Rows.Count; row++)
{
rawData[row + 1, col] = dt.Rows[row].ItemArray[col];
}
}
// Calculate the final column letter
string finalColLetter = string.Empty;
string colCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int colCharsetLen = colCharset.Length;
if (dt.Columns.Count > colCharsetLen)
{
finalColLetter = colCharset.Substring((dt.Columns.Count - 1) / colCharsetLen - 1, 1);
}
finalColLetter += colCharset.Substring((dt.Columns.Count - 1) % colCharsetLen, 1);
workSheet.Name = dt.TableName;
// Fast data export to Excel
string excelRange = string.Format("A1:{0}{1}", finalColLetter, dt.Rows.Count + 1);
//The code crashes here (ONLY in SSIS):
workSheet.get_Range(excelRange, Type.Missing).Value2 = rawData;
// Mark the first row as BOLD
((Excel.Range)workSheet.Rows[1, Type.Missing]).Font.Bold = true;
}
List<int> lstColumnsToSum = new List<int>() { 9 };
Dictionary<int, string> dictColSumName = new Dictionary<int, string>() { { 9, "" } };
Dictionary<int, decimal> dictColumnSummation = new Dictionary<int, decimal>() { { 9, 0 } };
// rows
for (int i = 0; i < dataTable.Rows.Count; i++)
{
for (int j = 1; j <= dataTable.Columns.Count; j++)
{
workSheet.Cells[(i + 2), (j)] = dataTable.Rows[i][j - 1];
if (lstColumnsToSum.Exists(x => (x == j)))
{
decimal val = 0;
if (decimal.TryParse(dataTable.Rows[i][j - 1].ToString(), out val))
{
dictColumnSummation[j] += val;
}
}
}
}
//Footer
int footerRowIdx = 2 + dataTable.Rows.Count;
foreach (var summablecolumn in dictColSumName)
{
workSheet.Cells[footerRowIdx, summablecolumn.Key] = String.Format("{0}", dictColumnSummation[summablecolumn.Key]);
}
// check fielpath
if (excelFilePath != null && excelFilePath != "")
{
try
{
if (File.Exists(excelFilePath))
File.Delete(excelFilePath);
workSheet.Activate();
workSheet.Application.ActiveWindow.SplitRow = 1;
workSheet.Application.ActiveWindow.FreezePanes = true;
int row = 1;
int column = 1;
foreach (var item in dataTable.Columns)
{
Excel.Range range = workSheet.Cells[row, column] as Excel.Range;
range.NumberFormat = "#";
range.EntireColumn.AutoFit();
range.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGray);
column++;
}
Excel.Range InternalCalculatedAmount = workSheet.Cells[1, 9] as Excel.Range;
InternalCalculatedAmount.EntireColumn.NumberFormat = "#0.00";
InternalCalculatedAmount.Columns.AutoFit();
workSheet.SaveAs(excelFilePath);
}
catch (System.Exception ex)
{
throw new System.Exception("Excel file could not be saved! Check filepath." + Environment.NewLine + ex.Message);
}
}
else // no filepath is given
{
excelApp.Visible = true;
}
}
catch (System.Exception ex)
{
throw new System.Exception("ex.Message + Environment.NewLine, ex.InnerException);
}
}
The exception thrown is a System.OutOfMemoryException when trying to execute the following piece of code:
workSheet.get_Range(excelRange, Type.Missing).Value2 = rawData;
My biggest frustration is that this method works 100% in a regular C# application.
The DataTable contains about 435000 rows. I know it's quite a bit of data but I use this very method, modified of course, to split data across multiple Excel worksheets in one of my other applications, and that DataSet contains about 1.1m rows. So less than half of my largest DataSet should be a walk-in-the-park...
Any light shed on this matter would be amazing!

iteration through datagrid rows for export to excel (again)

I am trying to export dataGrid rows to an Excel sheet.
Since I am switching from a WinForms(dataGridView) to WPF
(dataGrid) and basically I have no clue about WPF so far
I need your help.
Maybe somebody can either tell me how to change my loop
or what I have to do instead to get the rows filled into
the cells of the Excel sheet.
I have read all articles on SO covering this problem but
don't seem to find a topic suiting my issue.
This is what I did for the filling of the column names, which
works perfectly:
for (int i = 1; i < dataGrid.Columns.Count + 1; i++)
{
Excel.Range BackgroundColor;
BackgroundColor = xlWorkSheet.get_Range("a9", "j9");
BackgroundColor.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.RoyalBlue);
AxlEx.Cells[9, i] = dataGrid.Columns[i - 1].Header;
}
when it comes down to the filling of the cells with rows I have tried numerous attemps to get it working
for (int i = 0; i < dataGrid.Items.Count; i++)
{
DataRowView aux = (DataRowView)dataGrid.Items[i];
for (int j = 0; j < aux.Row.ItemArray.Length; j++)
{
//Console.WriteLine(string.Format("{0}-{1}", j, aux.Row.ItemArray[j]));
AxlEx.Cells[i + 10, j + 1] = aux.Row.ItemArray[j];
}
}
throws me an exception of System.InvalidCast exception for a type mismatch
which is obvious... but I don't know how to convert, here also the fitting
Topics on SO didn't have an example which i could understand to change my code.
Before I had this:
for (int i = 0; i < dataGrid.Items.Count; i++)
{
for (int j = 0; j < dataGrid.Columns.Count; j++)
{
AxlEx.Cells[i + 10, j + 1] = dataRow.Row.ItemArray[j].ToString();
}
}
which then works for 1 row if i refer to
DataRowView dataRow = (DataRowView)dataGrid.SelectedItem;
How can I get this to work?
I do not know whether it is necessary to debug your code. However, I would like to show my work code to export data from DataGrid to MS Excel:
It is better to transfer this work from UI Thread to a ThreadPool:
using Excel = Microsoft.Office.Interop.Excel;//add this library
Task.Run(() => {
// load excel, and create a new workbook
Excel.Application excelApp = new Excel.Application();
excelApp.Workbooks.Add();
// single worksheet
Excel._Worksheet workSheet = excelApp.ActiveSheet;
// column headings
for (int i = 0; i < YourDataTable.Columns.Count; i++)
{
workSheet.Cells[1, (i + 1)] = YourDataTable.Columns[i].ColumnName;
}
// rows
for (int i = 0; i < YourDataTable.Rows.Count; i++)
{
// to do: format datetime values before printing
for (int j = 0; j < YourDataTable.Columns.Count; j++)
{
workSheet.Cells[(i + 2), (j + 1)] = YourDataTable.Rows[i][j];
}
}
excelApp.Visible = true;
});
I found the problem....
for (int i = 0; i < dataGrid.Items.Count-1; i++)
{
DataRowView aux = (DataRowView)dataGrid.Items[i];
for (int j = 0; j < aux.Row.ItemArray.Length; j++)
{
//Console.WriteLine(string.Format("{0}-{1}", j, aux.Row.ItemArray[j]));
AxlEx.Cells[i + 10, j + 1] = aux.Row.ItemArray[j];
}
}
i had to substract (dataGrid.Items.Count-1) because there was an additional blank line in the dataGrid which seemed to cause the problem.
Pobably due to a NULL field return value ???
the datagrid

How to remove a column from excel sheet in epplus

I'm using csharp to insert data into excel sheet into 7 columns. The interface of this program will allow users to select 7 checkboxes. If they select all 7, all the 7 columns in spreadhseet will have data, if they select one checkbox then only one column will have data. I have got a for loop which will check if data is there, if no data exists, I want to remove that column in epplus. Here's a previous discussion on this topic
How can I delete a Column of XLSX file with EPPlus in web app
It's quiet old so I just wanna check if there's a way to do this.
Or, is there a way to cast epplus excel sheet to microsoft interop excel sheet and perform some operations.
Currently, I've code like this:
for(int j=1; j <= 9; j++) //looping through columns
{
int flag = 0;
for(int i = 3; i <= 10; i++) // looping through rows
{
if(worksheet.cells[i, j].Text != "")
{
flag ++;
}
}
if (flag == 0)
{
worksheet.column[j].hidden = true; // hiding the columns- want to remove it
}
}
Can we do something like:
Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
xlApp = worksheet; (where worksheet is epplus worksheet)
Are you using EPPlus 4? The ability to do column inserts and deletion was added with the new Cell store model they implemented. So you can now do something like this:
[TestMethod]
public void DeleteColumn_Test()
{
//http://stackoverflow.com/questions/28359165/how-to-remove-a-column-from-excel-sheet-in-epplus
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
if (existingFile.Exists)
existingFile.Delete();
//Throw in some data
var datatable = new DataTable("tblData");
datatable.Columns.Add(new DataColumn("Col1"));
datatable.Columns.Add(new DataColumn("Col2"));
datatable.Columns.Add(new DataColumn("Col3"));
for (var i = 0; i < 20; i++)
{
var row = datatable.NewRow();
row["Col1"] = "Col1 Row" + i;
row["Col2"] = "Col2 Row" + i;
row["Col3"] = "Col3 Row" + i;
datatable.Rows.Add(row);
}
using (var pack = new ExcelPackage(existingFile))
{
var ws = pack.Workbook.Worksheets.Add("Content");
ws.Cells.LoadFromDataTable(datatable, true);
ws.DeleteColumn(2);
pack.SaveAs(existingFile);
}
}

Fastest way to drop a DataSet into a worksheet

A rather higeisch dataset with 16000 x 12 entries needs to be dumped into a worksheet.
I use the following function now:
for (int r = 0; r < dt.Rows.Count; ++r)
{
for (int c = 0; c < dt.Columns.Count; ++c)
{
worksheet.Cells[c + 1][r + 1] = dt.Rows[r][c].ToString();
}
}
I rediced the example to the center piece
Here is what i implemented after reading the suggestion from Dave Zych.
This works great.
private static void AppendWorkSheet(Excel.Workbook workbook, DataSet data, String tableName)
{
Excel.Worksheet worksheet;
if (UsedSheets == 0) worksheet = workbook.Worksheets[1];
else worksheet = workbook.Worksheets.Add();
UsedSheets++;
DataTable dt = data.Tables[0];
var valuesArray = new object[dt.Rows.Count, dt.Columns.Count];
for (int r = 0; r < dt.Rows.Count; ++r)
{
for (int c = 0; c < dt.Columns.Count; ++c)
{
valuesArray[r, c] = dt.Rows[r][c].ToString();
}
}
Excel.Range c1 = (Excel.Range)worksheet.Cells[1, 1];
Excel.Range c2 = (Excel.Range)worksheet.Cells[dt.Rows.Count, dt.Columns.Count];
Excel.Range range = worksheet.get_Range(c1, c2);
range.Cells.Value2 = valuesArray;
worksheet.Name = tableName;
}
Build a 2D array of your values from your DataSet, and then you can set a range of values in Excel to the values of the array.
object valuesArray = new object[dataTable.Rows.Count, dataTable.Columns.Count];
for(int i = 0; i < dt.Rows.Count; i++)
{
//If you know the number of columns you have, you can specify them this way
//Otherwise use an inner for loop on columns
valuesArray[i, 0] = dt.Rows[i]["ColumnName"].ToString();
valuesArray[i, 1] = dt.Rows[i]["ColumnName2"].ToString();
...
}
//Calculate the second column value by the number of columns in your dataset
//"O" is just an example in this case
//Also note: Excel is 1 based index
var sheetRange = worksheet.get_Range("A2:O2",
string.Format("A{0}:O{0}", dt.Rows.Count + 1));
sheetRange.Cells.Value2 = valuesArray;
This is much, much faster than looping and setting each cell individually. If you're setting each cell individually, you have to talk to Excel through COM (for lack of a better phrase) for each cell (which in your case is ~192,000 times), which is incredibly slow. Looping, building your array and only talking to Excel once removes much of that overhead.

Categories