I have some code that's creating a DataTable, and then converting it to an Excel spreadsheet. Code is below:
DataTable table = (DataTable)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(exportDtos), typeof(DataTable));
using var stream = new MemoryStream();
using (SpreadsheetDocument document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook))
{
WorkbookPart workbookPart = document.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
var sheetData = new SheetData();
worksheetPart.Worksheet = new Worksheet(sheetData);
Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());
Sheet sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart), SheetId = 1, Name = "Sheet1" };
sheets.Append(sheet);
Row headerRow = new Row();
List<String> columns = new List<string>();
foreach (DataColumn column in table.Columns)
{
columns.Add(column.ColumnName);
Cell cell = new Cell();
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(column.ColumnName);
headerRow.AppendChild(cell);
}
sheetData.AppendChild(headerRow);
foreach (DataRow dsrow in table.Rows)
{
Row newRow = new Row();
foreach (String col in columns)
{
Cell cell = new Cell();
var celltype = dsrow[col].GetType();
if (dsrow[col].GetType() == typeof(double))
{
cell.DataType = CellValues.Number;
cell.CellValue = new CellValue(decimal.Parse(dsrow[col].ToString()));
cell.StyleIndex = 2;
}
else if (dsrow[col].GetType() == typeof(DateTime))
{
cell.DataType = CellValues.Date;
cell.CellValue = new CellValue(DateTime.Parse(dsrow[col].ToString()).ToOADate());
cell.StyleIndex = 1;
}
else
{
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(dsrow[col].ToString());
}
newRow.AppendChild(cell);
}
sheetData.AppendChild(newRow);
}
workbookPart.Workbook.Save();
}
The problem is that dates and decimal values were stored as text, and not sorting correctly in Excel, so I added these two conditions:
if (dsrow[col].GetType() == typeof(double))
{
cell.DataType = CellValues.Number;
cell.CellValue = new CellValue(decimal.Parse(dsrow[col].ToString()));
cell.StyleIndex = 2;
}
else if (dsrow[col].GetType() == typeof(DateTime))
{
cell.DataType = CellValues.Date;
cell.CellValue = new CellValue(DateTime.Parse(dsrow[col].ToString()).ToOADate());
cell.StyleIndex = 1;
}
else
{
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(dsrow[col].ToString());
}
There's a few problems:
The StyleIndex doesn't work for the double values, it's not formatting the cell with 2 decimal places. It also gives this Repair error:
We found a problem with some content in 'file.xlsx' ... Excel was able to open the file by repairing or removing the unreadable content.
Excel completed file level validation and repair. Some parts of this workbook may have been repaired or discarded.
If I remove the StyleIndex, I don't get the error, but it still doesn't format the cell with 2 decimal places.
The date value is always a number value, not a date. And it always gives the repair error.
I've looked at several different forums saying to try different things, and I can't get any of them to work. I've changed the StyleIndex to different suggested numbers, changed the DataType, and it's always the same problems. Is there a consistent answer to how to format dates and currency values?
Related
I have created a download button in C#/asp.net, which takes a GridView, converts it to a data table, and then stores it into a .xlsx file. What I want to do after is be able to change the active tab of the spreadsheet I created.
Here is the code below:
protected void downloadBtn_Click(object sender, EventArgs e)
{
using (var spreadSheet = SpreadsheetDocument.Create(Server.MapPath("~/Downloads/TestSheet.xlsx"), DocumentFormat.OpenXml.SpreadsheetDocumentType.Workbook))
{
WorkbookPart workbookPart = spreadSheet.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet(new SheetData());
//Fine up to this point
spreadSheet.WorkbookPart.Workbook.Sheets = new Sheets();
DataTable table = new DataTable();
//Converts GridView into Data Table **
for (int i = 0; i < gvEmployee.HeaderRow.Cells.Count - 1; i++)
{
table.Columns.Add(gvEmployee.HeaderRow.Cells[i + 1].Text);
}
// fill rows
for (int i = 0; i < gvEmployee.Rows.Count; i++)
{
DataRow dr = table.NewRow();
for (int j = 0; j < gvEmployee.Columns.Count - 1; j++)
{
dr[j] = gvEmployee.Rows[i].Cells[j + 1].Text;
}
table.Rows.Add(dr);
}
var sheetPart = spreadSheet.WorkbookPart.AddNewPart<WorksheetPart>();
var sheetData = new SheetData();
sheetPart.Worksheet = new Worksheet(sheetData);
Sheets sheets = spreadSheet.WorkbookPart.Workbook.GetFirstChild<Sheets>();
string relationshipId = spreadSheet.WorkbookPart.GetIdOfPart(sheetPart);
uint sheetId = 1;
if (sheets.Elements<Sheet>().Count() > 0)
{
sheetId = sheets.Elements<Sheet>().Select(s => s.SheetId.Value).Max() + 1;
}
Sheet sheet = new Sheet() { Id = relationshipId, SheetId = sheetId, Name = "TestSheet" };
sheets.Append(sheet);
Row headerRow = new Row();
List<String> columns = new List<string>();
foreach (DataColumn column in table.Columns)
{
columns.Add(column.ColumnName);
Cell cell = new Cell();
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(column.ColumnName);
headerRow.AppendChild(cell);
}
sheetData.AppendChild(headerRow);
foreach (DataRow dsrow in table.Rows)
{
Row newRow = new Row();
foreach (String col in columns)
{
Cell cell = new Cell();
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(dsrow[col].ToString()); //
newRow.AppendChild(cell);
}
sheetData.AppendChild(newRow);
}
Sheet sheet2 = new Sheet() { Id = spreadSheet.WorkbookPart.GetIdOfPart(sheetPart), SheetId = 2, Name = "AdditionalSheet" };
sheets.Append(sheet2);
var sheetIndex = workbookPart.Workbook.Descendants<Sheet>().ToList().IndexOf(sheet2);
WorkbookView workbookView = workbookPart.Workbook.Descendants<WorkbookView>().FirstOrDefault(); //new WorkbookView();
workbookView.ActiveTab = Convert.ToUInt32(sheetIndex);
workbookPart.Workbook.Save();
spreadSheet.Close();
}
}
When I try to run this code, it throws an error on the "workbookView.ActiveTab" at the last few lines stating
System.NullReferenceException: 'Object reference not set to an instance of an object.'
workbookView was null.
Any ideas as to why its doing this?
Okay, I just figured out what was wrong, there was no WorkbookView initialized, as I thought it thought it didn't need to be. But you have to actually make a WorkbookView of your workbook before you can change it. I just needed this one line:
workbookPart.Workbook.Append(new BookViews(new WorkbookView()));
I identified the code slowing down the process as this one (where I'm filling the cells):
What I'm doing here is basically loading some data from a database using a DataSet.
Microsoft.Office.Interop.Excel.Range range1 = null;
Microsoft.Office.Interop.Excel.Range cell1 = null;
Microsoft.Office.Interop.Excel.Borders border1 = null;
for (i = 0; i <= ds.Tables[0].Rows.Count - 1; i++)
{
int s = i + 1;
for (j = 0; j <= ds.Tables[0].Columns.Count - 1; j++)
{
data = ds.Tables[0].Rows[i].ItemArray[j].ToString();
xlWorkSheet.Cells[s + 1, j + 1] = data;
range1 = xlWorkSheet.UsedRange;
cell1 = range1.Cells[s + 1, j + 1];
border1 = cell1.Borders;
if (((IList)terms).Contains(xlWorkSheet.Cells[1, j + 1].Value.ToString()))
{
cell1.Interior.Color = System.Drawing.Color.Red;
}
range1.Columns.AutoFit();
range1.HorizontalAlignment = Microsoft.Office.Interop.Excel.XlHAlign.xlHAlignCenter;
border1.LineStyle = Microsoft.Office.Interop.Excel.XlLineStyle.xlContinuous;
border1.Weight = 2d;
}
}
It's sometimes taking like more than 1 minute to load the whole thing. Is there is away to optimize it?.
Cell-by-cell is the slowest possible way to interact with Excel using Interop - look up how to add data to a sheet from an array in one operation.
E.g.
Write Array to Excel Range
shows this approach.
Interop libraries are extremely slow and spends huge source of system.
Instead of using Interop Libraries to create Excel files, you can simply use it OpenXML library.
I'm using it in production. And over 1 million rows it just takes about 10 seconds to export dataset to excel file.
Here is a sample code quoted from:
Export DataTable to Excel with Open Xml SDK in c#
private void ExportDSToExcel(DataSet ds, string destination)
{
using (var workbook = SpreadsheetDocument.Create(destination, DocumentFormat.OpenXml.SpreadsheetDocumentType.Workbook))
{
var workbookPart = workbook.AddWorkbookPart();
workbook.WorkbookPart.Workbook = new DocumentFormat.OpenXml.Spreadsheet.Workbook();
workbook.WorkbookPart.Workbook.Sheets = new DocumentFormat.OpenXml.Spreadsheet.Sheets();
uint sheetId = 1;
foreach (DataTable table in ds.Tables)
{
var sheetPart = workbook.WorkbookPart.AddNewPart<WorksheetPart>();
var sheetData = new DocumentFormat.OpenXml.Spreadsheet.SheetData();
sheetPart.Worksheet = new DocumentFormat.OpenXml.Spreadsheet.Worksheet(sheetData);
DocumentFormat.OpenXml.Spreadsheet.Sheets sheets = workbook.WorkbookPart.Workbook.GetFirstChild<DocumentFormat.OpenXml.Spreadsheet.Sheets>();
string relationshipId = workbook.WorkbookPart.GetIdOfPart(sheetPart);
if (sheets.Elements<DocumentFormat.OpenXml.Spreadsheet.Sheet>().Count() > 0)
{
sheetId =
sheets.Elements<DocumentFormat.OpenXml.Spreadsheet.Sheet>().Select(s => s.SheetId.Value).Max() + 1;
}
DocumentFormat.OpenXml.Spreadsheet.Sheet sheet = new DocumentFormat.OpenXml.Spreadsheet.Sheet() { Id = relationshipId, SheetId = sheetId, Name = table.TableName };
sheets.Append(sheet);
DocumentFormat.OpenXml.Spreadsheet.Row headerRow = new DocumentFormat.OpenXml.Spreadsheet.Row();
List<String> columns = new List<string>();
foreach (DataColumn column in table.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 table.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);
}
}
}
}
My code works fine except keeping numbers as numbers. If statement catches the double value when I debug. Yet I cannot write in a data type except string to Excel. If I should be more precise, the cells in Excel are correct but they are not numbers.
foreach (System.Data.DataRow dsrow in table.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();
if (dsrow[col].GetType() == typeof(Double))
{
cell.DataType = DocumentFormat.OpenXml.Spreadsheet.CellValues.Number;
}
else
{
cell.DataType = DocumentFormat.OpenXml.Spreadsheet.CellValues.String;
}
cell.CellValue = new DocumentFormat.OpenXml.Spreadsheet.CellValue(dsrow[col].ToString()); //
newRow.AppendChild(cell);
}
sheetData.AppendChild(newRow);
}
I have struggled with the same problem.
It appears that the problem appears if you are using a locale with comma as decimal separator. In the XML it should be american format.
So specifying CutureInfo in ToString worked for me:
var dataRow = new Row();
var cell = new Cell();
cell.CellValue = new CellValue(1.234.ToString(new CultureInfo("en-US")));
cell.DataType = CellValues.Number;
dataRow.AppendChild(cell);
sheetData.AppendChild(dataRow);
I am trying to insert the text in existing cell by OpenXml, but it's not reflecting the excel sheet, Please any help me to!
static void InsertTextInCell(WorksheetPart worksheetPart)
{
Worksheet workSheet = worksheetPart.Worksheet;
SheetData sheetData = workSheet.GetFirstChild<SheetData>();
Cell cell = new Cell()
{
CellReference = "E8",
DataType = CellValues.String,
CellValue = new CellValue("Adding Value")
};
sheetData.Append(cell);
}
or
static void InsertTextInCell(WorkbookPart wbPart, string sheetName)
{
Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().
Where(s => s.Name == sheetName).FirstOrDefault();
WorksheetPart wsPart =
(WorksheetPart)(wbPart.GetPartById(theSheet.Id));
Cell theCell = wsPart.Worksheet.Descendants<Cell>().
Where(c => c.CellReference == "A8").FirstOrDefault();
CellValue cellValue2 = new CellValue();
cellValue2.Text = "1test";
theCell.Append(cellValue2);
}
Thanks,
Saran
You can refer below code snippet to insert in text in excel
// Creates an SheetData instance and adds its children.
public SheetData GenerateSheetData()
{
SheetData sheetData1 = new SheetData();
Row row1 = new Row(){ RowIndex = (UInt32Value)1U, Spans = new ListValue<StringValue>() { InnerText = "1:1" } };
Cell cell1 = new Cell(){ CellReference = "A1", DataType = CellValues.SharedString };
CellValue cellValue1 = new CellValue();
cellValue1.Text = "0";
cell1.Append(cellValue1);
row1.Append(cell1);
Row row2 = new Row(){ RowIndex = (UInt32Value)2U, Spans = new ListValue<StringValue>() { InnerText = "1:1" } };
Cell cell2 = new Cell(){ CellReference = "A2", DataType = CellValues.SharedString };
CellValue cellValue2 = new CellValue();
cellValue2.Text = "1";
cell2.Append(cellValue2);
row2.Append(cell2);
sheetData1.Append(row1);
sheetData1.Append(row2);
return sheetData1;
}
static void InsertTextInCell(WorkbookPart wbPart, string sheetName, string addressName)
{
Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().
Where(s => s.Name == sheetName).FirstOrDefault();
WorksheetPart wsPart =
(WorksheetPart)(wbPart.GetPartById(theSheet.Id));
Cell theCell = wsPart.Worksheet.Descendants<Cell>().
Where(c => c.CellReference == addressName).FirstOrDefault();
if (theCell.DataType == null)
{
theCell.CellValue = new CellValue("AddString");
}
}
I am new to this and when I try to add more than one cell to a row it says there is unreadable content. Here is what I have.
SpreadsheetDocument ssDoc = SpreadsheetDocument.Create(saveFile, SpreadsheetDocumentType.Workbook);
// Add a WorkbookPart to the document
WorkbookPart workbookPart = ssDoc.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
// Add a WorksheetPart to theWorkbookPart
WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet(new SheetData());
Sheets sheets = ssDoc.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());
Sheet sheet = new Sheet()
{ Id = ssDoc.WorkbookPart.GetIdOfPart(worksheetPart),
SheetId = 1, Name = "Sheet1"
};
sheets.Append(sheet);
Worksheet worksheet = new Worksheet();
SheetData sheetData = new SheetData();
Row row = new Row();
Cell cell = new Cell()
{
CellReference = "A1",
DataType = CellValues.String,
CellValue = new CellValue("Cell1")
};
Cell cell2 = new Cell()
{
CellReference = "A2",
DataType = CellValues.String,
CellValue = new CellValue("Cell2")
};
row.Append(cell);
row.Append(cell2);
sheetData.Append(row);
worksheet.Append(sheetData);
worksheetPart.Worksheet = worksheet;
// Close the document.
ssDoc.Close();
If I remove the second cell, it works as expected.
Instead of "A2" the cell reference should be "B1"
SpreadSheet.Cell cell = new SpreadSheet.Cell()
{
CellReference = "A1",
DataType = SpreadSheet.CellValues.String,
CellValue = new SpreadSheet.CellValue("Cell1")
};
SpreadSheet.Cell cell2 = new SpreadSheet.Cell()
{
CellReference = "B1",
DataType = SpreadSheet.CellValues.String,
CellValue = new SpreadSheet.CellValue("Cell2")
};
I had a similar issue. If anyone wants to insert cells in the same column, Row Index needs to be incremented and cell reference in order to insert cell in the same column. Took me entire weekend to figure out :D
Row row2 = new Row { RowIndex = 2};
SpreadSheet.Cell cell2 = new SpreadSheet.Cell()
{
CellReference = "B1",
DataType = SpreadSheet.CellValues.String,
CellValue = new SpreadSheet.CellValue("Cell2")
};
row2.Append(cell2);
sheetData.Append(row);
It's better to use loop.