I have a huge data that needs to be written to excel from database in chunks using OpenXML.
I read a set of data ( 1000 rows) from database and write to excel for the first time. I loop and read the second set of data ( next 1000 rows) and have to append it to the existing excel rows.
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(destination, true))
{
WorkbookPart workbookPart = spreadSheet.WorkbookPart;
IEnumerable<Sheet> sheets = spreadSheet.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
string relationshipId = sheets.FirstOrDefault(s => string.Compare(s.Name, "Sheet 1", true) == 0).Id;
WorksheetPart worksheetPart = (WorksheetPart)spreadSheet.WorkbookPart.GetPartById(relationshipId);
SheetData sheetData = new SheetData();
DocumentFormat.OpenXml.Spreadsheet.Row headerRow = new DocumentFormat.OpenXml.Spreadsheet.Row();
List<String> columns = new List<string>();
System.Data.DataTable dt = ds.Tables[0];
foreach (DataColumn column in dt.Columns)
{
columns.Add(column.ColumnName);
DocumentFormat.OpenXml.Spreadsheet.Cell cell = new DocumentFormat.OpenXml.Spreadsheet.Cell();
cell.DataType = DocumentFormat.OpenXml.Spreadsheet.CellValues.String;
cell.CellValue = new DocumentFormat.OpenXml.Spreadsheet.CellValue(column.ColumnName);
headerRow.AppendChild(cell);
}
sheetData.AppendChild(headerRow);
foreach (DataRow dsrow in dt.Rows)
{
DocumentFormat.OpenXml.Spreadsheet.Row newRow = new DocumentFormat.OpenXml.Spreadsheet.Row();
foreach (String col in columns)
{
DocumentFormat.OpenXml.Spreadsheet.Cell cell = new DocumentFormat.OpenXml.Spreadsheet.Cell();
cell.DataType = DocumentFormat.OpenXml.Spreadsheet.CellValues.String;
cell.CellValue = new DocumentFormat.OpenXml.Spreadsheet.CellValue(dsrow[col].ToString());
newRow.AppendChild(cell);
}
sheetData.AppendChild(newRow);
}
worksheetPart.Worksheet.ReplaceChild<SheetData>(sheetData, worksheetPart.Worksheet.Elements<SheetData>().FirstOrDefault());
I'm not sure how of you do it on SpreadSheetDocument by Microsoft, I have used a more simpler 3rd party DLL which uses SpreadSheetDocument and has minimized the programmer's work.
Anyhow, if you know the specific row index from where the next set of data should be written from, say 1002 in your case (1000 rows and 1 row of header). (Supposedly save in a Session variable or dynamically get the start index by knowing how many rows of data has been written in the excel sheet)
I think you could do something like,
int rowIndex = 1002; //This you derive
DocumentFormat.OpenXml.Spreadsheet.Row newRow = new DocumentFormat.OpenXml.Spreadsheet.Row() { RowIndex = rowIndex };
Note:
The { RowIndex = rowIndex } should be considered only in the first instance of the loop when you begin appending.
Hope this helps.
I'm using OpenXML in my ASP.NET application. Here is the code for generating it:
MemoryStream ms = new System.IO.MemoryStream();
SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(ms, SpreadsheetDocumentType.Workbook);
WorkbookPart workbookPart = spreadsheetDocument.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
UInt32Value rowIndex = 0;
SheetData sheetData = new SheetData();
Row r1 = new Row() { RowIndex = rowIndex };
Cell c1 = new Cell() { DataType = CellValues.String, CellValue=new CellValue("col1") };
Cell c2 = new Cell() { DataType = CellValues.String, CellValue = new CellValue("col2") };
Cell c3 = new Cell() { DataType = CellValues.String, CellValue = new CellValue("col3") };
Cell c4 = new Cell() { DataType = CellValues.String, CellValue = new CellValue("col4") };
r1.Append(new List<Cell>() { c1, c2, c3, c4 });
rowIndex++;
sheetData.Append(r1);
foreach (Rezultat rez in rezultati)
{
Row r2 = new Row() { RowIndex = rowIndex };
Cell c1 = new Cell() { DataType = CellValues.String, CellValue = new CellValue(rez.a) };
Cell c2 = new Cell() { DataType = CellValues.String, CellValue = new CellValue(rez.b) };
Cell c3 = new Cell() { DataType = CellValues.String, CellValue = new CellValue(rez.c) };
Cell prolaz = new Cell() { DataType = CellValues.String };
if (rez.d)
{
prolaz.CellValue = new CellValue("DA");
}
else
{
prolaz.CellValue = new CellValue("NE");
}
r2.Append(new List<Cell>() { c1,c2,c3,c4 });
rowIndex++;
sheetData.Append(r2);
}
worksheetPart.Worksheet.Append(sheetData);
Sheets sheets = new Sheets();
Sheet sheet = new Sheet();
sheet.Name = "first";
sheet.SheetId = 1;
sheet.Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart);
sheets.Append(sheet);
spreadsheetDocument.WorkbookPart.Workbook.AppendChild<Sheets>(sheets);
spreadsheetDocument.WorkbookPart.Workbook.Save();
spreadsheetDocument.Close();
string fileName = "testOpenXml.xlsx";
Response.Clear();
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", string.Format("attachment; filename={0}", fileName));
ms.WriteTo(Response.OutputStream);
ms.Close();
Response.End();
When I download it, and try to open it in excel, I got message that excel found unreadable context in excel.I suppose I have to change the way how I geenrate excel. I tried couple of solutions, some gave same error and some open excel, but with no data in it.
You will get this error if the cells do not appear in order in the file.
If you are using Microsoft's Sample Code in "How to: Insert text into a cell in a spreadsheet document (Open XML SDK)" in DevCenter you will see there is a function called InsertCellInWorksheet. This function has the defective line in it:
if (string.Compare(cell.CellReference.Value, cellReference, true) > 0)
If you have more than 26 columns, this will cause them to be sorted like this:
A1 AA1 AB1 B1 C1 D1 E1 ....
The result is you will see the data in column A and from AA on. Columns B through Z will be missing and you will get the dreaded "Excel found unreadable content" message.
You need to replace this string.Compare with a function that will put AA1 after Z1, or... if you know you are creating everything in order change it to if (false).
What you want to create are cells of type InlineString instead of String.
Below is a snippet to achieve part of what you need. Hopefully, you can modify it to your needs.
SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(#"test.xlsx", SpreadsheetDocumentType.Workbook);
WorkbookPart workbookPart = spreadsheetDocument.AddWorkbookPart();
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
Row row = new Row { RowIndex = 1 };
Cell cell1 = new Cell { DataType = CellValues.InlineString };
InlineString inlineString1 = new InlineString();
Text text = new Text { Text = "col1" };
inlineString1.Append(text);
cell1.Append(inlineString1);
Cell cell2 = new Cell { DataType = CellValues.InlineString };
InlineString inlineString2 = new InlineString();
Text text2 = new Text { Text = "col2" };
inlineString2.Append(text2);
cell2.Append(inlineString2);
row.Append(new List<Cell>() { cell1, cell2 });
SheetData sheetData = new SheetData();
sheetData.Append(row);
Worksheet worksheet = new Worksheet();
worksheet.Append(sheetData);
worksheetPart.Worksheet = worksheet;
worksheetPart.Worksheet.Save();
Sheets sheets = new Sheets();
string relId = workbookPart.GetIdOfPart(worksheetPart);
Sheet sheet = new Sheet { Name = "Sheet1", SheetId = 1, Id = relId };
sheets.Append(sheet);
Workbook workbook = new Workbook();
workbook.Append(sheets);
spreadsheetDocument.WorkbookPart.Workbook = workbook;
spreadsheetDocument.WorkbookPart.Workbook.Save();
spreadsheetDocument.Close();
I know this is an old question, but I ran into this problem and found the solution in a MSDN forum. The SDK samples contain code that simply does not work with columns beyong Z (like AA for example).
As mentioned here, the problem is that using alphabetial order, AA1 will come before B1 and so on. The way to resolve it is to make the letters base-26 numbers. In the very same MSDN forum, the solution was given.
First create a base-26 converter:
//Hexavigesimal (Excel Column Name to Number) - Bijective
private int fromBase26(string colName)
{
colName = colName.ToUpper();
int decimalValue = 0;
for (int i = 0; i < colName.Length; i++)
{
decimalValue *= 26;
decimalValue += (colName[i] - 64);
}
return decimalValue;
}
Then, change this line in the method to insert a cell in the worsheet from:
if (string.Compare(cell.CellReference.Value, cellReference, true) > 0)
To:
if (fromBase26(Regex.Replace(cell.CellReference.Value, #"[\d-]", string.Empty)) > fromBase26(Regex.Replace(cellReference, #"[\d-]", string.Empty)))
I need to delete certain rows from an excel file. The excel file is a template and may have extra rows, which have to be deleted for the final output. However deletion of the cells corrupts the file. The code is as simple as follows
Worksheet worksheet = worksheetPart.Worksheet;
SheetData sheetData = worksheet.GetFirstChild<SheetData>();
foreach (var row in sheetData.Elements<Row>().
Where(r => r.RowIndex.Value >= rowIndex && r.RowIndex.Value < rowIndex + count).ToList())
{
row.Remove();
}
Alternatively, I tried setting the existing cells to blank values.
Worksheet worksheet = worksheetPart.Worksheet;
SheetData sheetData = worksheet.GetFirstChild<SheetData>();
foreach (var row in sheetData.Elements<Row>().
Where(r => r.RowIndex.Value >= rowIndex && r.RowIndex.Value < rowIndex + count).ToList())
{
IEnumerable<Cell> cells = row.Elements<Cell>().ToList();
if (cells != null)
{
foreach (Cell cell in cells)
{
if (cell.CellValue != null && !String.IsNullOrEmpty(cell.CellValue.Text))
{
cell.DataType = new EnumValue<CellValues>(CellValues.String);
cell.CellValue = new CellValue("");
}
}
}
}
My file always gets corrupted.
The issue was that the final streams to save the worksheet/workbooks, had a mismatch in the sizes.
I am new to OpenXML and have been struggling with adding a new row with cell data in A1 after a row (with cell data as well) has been added.
So essentially I want to insert "Test" in "row 1" column "A1" and "test" in "row 2" column "A1".
Here is my code, it looks sound and creates the file however Excel does not open it. I opened it in OpenOffice and it only shows one row instead of two. When I comment out appending row2 to the sheetdata, it works fine. So I am thinking am creating the 2nd row incorrectly. Any help is appreciated. Thank you in advance. Here is the code:
using (SpreadsheetDocument spreadSheetDocument =
SpreadsheetDocument.Create("generated.xlsx", SpreadsheetDocumentType.Workbook))
{
//Add a WorkbookPart to the document.
WorkbookPart workbookpart = spreadSheetDocument.AddWorkbookPart();
//create new workbook
workbookpart.Workbook = new Workbook();
// Add a WorksheetPart to the WorkbookPart.
WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
//instantiate new worksheet with new sheetdata
worksheetPart.Worksheet = new Worksheet(new SheetData());
//Add Sheets to the Workbook.
Sheets sheets = spreadSheetDocument.WorkbookPart.Workbook.
AppendChild<Sheets>(new Sheets());
DocumentFormat.OpenXml.UInt32Value sheetId = 1;
//Append a new worksheet and associate it with the workbook.
Sheet sheet = new Sheet();
sheet.Id = spreadSheetDocument.WorkbookPart.GetIdOfPart(worksheetPart);
sheet.SheetId = sheetId;
sheet.Name = new StringValue("test_" + 1);
sheets.Append(sheet);
//Get the sheetData cell table.
SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();
UInt32Value rowindex = 1;
UInt32Value rowindex2 = 2;
// Add a row to the cell table.
Row row = new Row() { RowIndex = rowindex };
Cell newCell = new Cell();
newCell.DataType = CellValues.InlineString;
newCell.CellReference = "A1";
InlineString inlineString = new InlineString();
Text t = new Text();
t.Text = "test";
inlineString.Append(t);
newCell.AppendChild(inlineString);
row.AppendChild(newCell);
sheetData.AppendChild(row);
rowindex++;
// Add a row to the cell table.
Row row2 = new Row() { RowIndex = rowindex2 };
Cell newCell2 = new Cell();
newCell2.DataType = CellValues.InlineString;
newCell2.CellReference = "A1";
InlineString inlineString2 = new InlineString();
Text t2 = new Text();
t2.Text = "test";
inlineString2.Append(t2);
newCell2.AppendChild(inlineString2);
row2.AppendChild(newCell2);
sheetData.AppendChild(row2);
workbookpart.Workbook.Save();
// Close the document.
spreadSheetDocument.Close();
MessageBox.Show("Success");
}
I am using Excel interop to create excel workbooks from my query results. When there are thousands of records it takes a long time for the workbook to be generated. The below code is a sample of how I am populating the cells.
RowNo = 1
For i = 1 To 4000
ColNo = 1
For j = 1 To 5
Dim cell As excel.Range = ws.Cells(RowNo, ColNo)
Dim value = j
cell.Value = value
ColNo = ColNo + 1
Next
RowNo = RowNo + 1
Next
For the above code to run it takes more than a minute. How can I optimize it?
Found the answer. You can write data to an array and then write the array to the excel range rather than writing the data cell by cell. See http://www.clear-lines.com/blog/post/Write-data-to-an-Excel-worksheet-with-C-fast.aspx
private static void WriteArray(int rows, int columns, Worksheet worksheet)
{
var data = new object[rows, columns];
for (var row = 1; row <= rows; row++)
{
for (var column = 1; column <= columns; column++)
{
data[row - 1, column - 1] = "Test";
}
}
var startCell = (Range)worksheet.Cells[1, 1];
var endCell = (Range)worksheet.Cells[rows, columns];
var writeRange = worksheet.Range[startCell, endCell];
writeRange.Value2 = data;
}