NPOI number format - c#

I'm using NPOI library to create excel file, I have problem with formatting price.
ISheet excelSheet = workbook.CreateSheet(sheetName);
ICellStyle codeCellStyle = workbook.CreateCellStyle();
ICellStyle priceCellStyle = workbook.CreateCellStyle();
ICellStyle availabilityStyle = workbook.CreateCellStyle();
excelSheet.SetColumnWidth(0, 10 * 256);
excelSheet.SetColumnWidth(1, 12 * 256);
excelSheet.SetColumnWidth(2, 15 * 256);
List<string> columns = new List<string>() { "Code", "Price", "Availability" };
IRow row = excelSheet.CreateRow(0);
foreach (var columnData in columns.Select((v, i) => new { Column = v, Index = i }).ToList())
{
row.CreateCell(columnData.Index).SetCellValue(columnData.Column);
}
codeCellStyle.DataFormat = workbook.CreateDataFormat().GetFormat("#");
priceCellStyle.DataFormat = workbook.CreateDataFormat().GetFormat("#,##0.00");
availabilityStyle.DataFormat = workbook.CreateDataFormat().GetFormat("#");
int rowIndex = 1;
foreach (var item in items)
{
row = excelSheet.CreateRow(rowIndex);
row.CreateCell(0).SetCellValue(item.Code);
row.Cells[0].CellStyle = codeCellStyle;
if (item.HasData)
{
if (item.Price == "ON DEMAND")
{
row.CreateCell(1).SetCellValue(item.Price);
}
else
{
ICell priceCell = row.CreateCell(1);
row.Cells[1].SetCellType(CellType.Numeric);
row.Cells[1].CellStyle = priceCellStyle;
if (percentage != 0)
priceCell.SetCellValue(double.Parse(item.Price) + ((double)percentage / (double)100) * double.Parse(item.Price));
else
priceCell.SetCellValue(double.Parse(item.Price));
}
row.CreateCell(2).SetCellValue(item.Availability ? "True" : "False");
}
else
{
row.CreateCell(1).SetCellValue("");
row.CreateCell(2).SetCellValue("");
}
rowIndex++;
}
When I have item with price "422,26" it produce excel file with price value 42226,00.
It works fine when I have price "380,00", "4730,00", but problem occurs when I have decimal part in string number.
I tried other suggestions from stackoverflow but I have no luck nothing works on my example.

After struggling this format solved my problem
"#,#0.00"

Related

Unable to insert cells into an Open XML document

Unable to insert cells into an Open XML document as the OpenXml Excel document. I think the issue is that the cell.Remove() isn't actually clearing cell. I'm unsure because the document says that it has been modified
public static void InsertCell(uint rowIndex, int columnIndex, string value, SheetData sheetData, bool appendRow, EnumValue<CellValues> dataType = null, uint? styleIndex = null)
{
//Row row = null;
// Check if the worksheet contains a row with the specified row index.
var row = sheetData.Elements<Row>().FirstOrDefault(r => r.RowIndex == rowIndex);
if (row == null)
{
row = new Row() { RowIndex = rowIndex };
sheetData.Append(row);
}
// Convert column index to column name for cell reference.
var columnName = GetExcelColumnName(columnIndex);
var cellReference = columnName + rowIndex; // e.g. A1
// Check if the row contains a cell with the specified column name.
var cell = row.Elements<Cell>().FirstOrDefault(c => c.CellReference.Value == cellReference);
//We need to delete the cell if it does exist as the InsertAt function does not delete the existing cell if it has been found
if (cell != null)
{
cell.Remove();
//cell.CellValue.Remove();
}
if (cell == null || cell.CellValue == null)
{
if (dataType == null)
dataType = new EnumValue<CellValues>(CellValues.SharedString);
var newCell = new Cell() { CellReference = cellReference, CellValue = new CellValue(value), DataType = dataType };
if (styleIndex != null)
newCell.StyleIndex = styleIndex;
if (row.ChildElements.Count < columnIndex)
row.AppendChild(newCell);
else
row.InsertAt(newCell, (int)columnIndex);
}
}
Just for reference this is the calling code.
public static string AddToMaterialsRegister(SpreadsheetDocument spreadsheetDocument, com.upvise.client.Query query, JSONObject[] forms)
{
WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
SheetData sheetData = worksheetPart.Worksheet.Elements<SheetData>().First();
Stylesheet styleSheet = workbookPart.WorkbookStylesPart.Stylesheet;
var stringTable = workbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
string text = "";
uint currentRow = GetLastUsedRow(sheetData);
for (int i = 0; i < forms.Length; i += 1)
{
long epochDate = forms[i].getLong("date");
epochDate += 10 * 60 * 60 * 1000; //Add 10 hours to get to the QLD timezone
//The date from the form + 1970
epochDate = (epochDate / 1000 / 60 / 60 / 24) + 25569;
JObject customObj = JObject.Parse(forms[i].getString("value"));
string amount = (string)customObj["F4"];
if (!amount.Equals("") && amount != null)
{
switch ((string)customObj["F5"])
{
case "1":
amount += "T";
break;
case "0":
amount += "m3";
break;
}
}
else
amount = "";
// Not important
string tipValue = forms[i].getString("templateid") == "" ? (string)customObj["F18"] : (string)customObj["F19"];
//Grab the photos from the form
var files = query.selectFiles("Forms.forms", forms[i].getString("id") + ":F22");
//int dateIndex = InsertSharedStringItem(epochDate.ToString(), stringTable);
int materialIndex = InsertSharedStringItem((string)customObj["F1"], stringTable);
int amountIndex = InsertSharedStringItem(amount, stringTable);
int deliveredIndex = InsertSharedStringItem((string)customObj["F6"] + " " + (tipValue != null ? tipValue : ""), stringTable);
int certIndex = InsertSharedStringItem(files.Length > 0 ? "Y" : "", stringTable);
int formNameIndex = InsertSharedStringItem(forms[i].getString("name"), stringTable);
InsertCell(currentRow, 1, epochDate.ToString(), sheetData, false, new EnumValue<CellValues>(CellValues.Number), 2);
InsertCell(currentRow, 2, materialIndex.ToString(), sheetData, false, new EnumValue<CellValues>(CellValues.SharedString), 3);
InsertCell(currentRow, 3, amountIndex.ToString(), sheetData, false, new EnumValue<CellValues>(CellValues.SharedString), 3);
InsertCell(currentRow, 4, deliveredIndex.ToString(), sheetData, false, new EnumValue<CellValues>(CellValues.SharedString), 4);
InsertCell(currentRow, 5, certIndex.ToString(), sheetData, false, new EnumValue<CellValues>(CellValues.SharedString), 4);;
InsertCell(currentRow, 6, formNameIndex.ToString(), sheetData, true, new EnumValue<CellValues>(CellValues.SharedString), 4);
currentRow += 1;
}
//spreadsheetDocument.Save();
//spreadsheetDocument.Close();
//worksheetPart.Worksheet.Save();
//workbookPart.Workbook.Save();
return forms.Length + " forms were added";
}
When I run the code it does not add rows to the xlsx.
Any help would be greatly appreciated.
I commented out the Row row = null; as that has wiping the row every time I inserted a new cell.

Displaying parsed csv files

Ive been working at this for hours and cant seem to figure out how to correctly display the data in a table
using (TextFieldParser csvParser = new TextFieldParser(path)) {
csvParser.CommentTokens = new string[] { "#" };
csvParser.SetDelimiters(new string[] { "," });
csvParser.HasFieldsEnclosedInQuotes = true;
csvParser.ReadLine();
int pointX = 30;
int pointY = 40;
while (!csvParser.EndOfData) {
string[] fields = csvParser.ReadFields();
int rowNums = fields.Length;
int index = 0;
for(index = 0; index < rowNums;index++) {
string Name = fields[index];
TextBox n = new TextBox();
n.Text = Name;
n.Location = new Point(pointX, pointY);
panel2.Controls.Add(n);
panel2.Show();
pointY += 20;
if(index != 0) {
pointX += 100;
}
}
}
}
Whats happening so far is im grabbing a csv file stored in the path variable and reading it the output is accessible through fields[] This works fine then I am trying to create textbox to put the data into based on rows however what i currently have comes out look like this
Program Display
I would like to display the column names and rows correctly in order here is an example image of what it looks like in notepad
Notepad Display
In notepad you will see each new line is a row and every , dictates a new entry in the row and i wanna display it this way in my program but in textbox
Also note that not all csv files that this program will be opening are short most will be large files with thousands or rows or more so theres no way that it could be simply putting fields[0] hard coded
You are much better off using a DataGridView to display this type of data in a table format.
From the toolbox add the DataGridView control to your form. You will need to build a DataTable that will bind to your DataGridview.
Below is what you can use(I commented out where you are skipping the header in your CSV file, and am using that line to get the column headers to be used in the datagrid)
var dt = new DataTable();
var lineNo = 0;
using (var csvParser = new TextFieldParser(path))
{
csvParser.CommentTokens = new string[] { "#" };
csvParser.SetDelimiters(new string[] { "," });
csvParser.HasFieldsEnclosedInQuotes = true;
//csvParser.ReadLine();
while (!csvParser.EndOfData)
{
var fields = csvParser.ReadFields();
var rowNums = fields.Length;
var row = dt.NewRow();
lineNo += 1;
int index = 0;
for (index = 0; index < rowNums; index++)
{
if (lineNo==1)
{
dt.Columns.Add(fields[index]);
}
else
{
row[index] = fields[index];
}
}
if (lineNo == 1) continue;
dt.Rows.Add(row);
dt.AcceptChanges();
}
}
dataGridView1.DataSource = dt;
I think the controls are overlapping, so you can not see them. That they are overlapping like chairs is the problem. You are not resetting your coordinates.
Here an improvement:
using (TextFieldParser csvParser = new TextFieldParser(path)) {
csvParser.CommentTokens = new string[] { "#" };
csvParser.SetDelimiters(new string[] { "," });
csvParser.HasFieldsEnclosedInQuotes = true;
csvParser.ReadLine();
int offsetX = 30;
int offsetY = 40;
int counter;
while (!csvParser.EndOfData) {
int pointerY = ++counter * offsetY; // first counter increments by one, then counter times offsetY occurs
int pointerX;
string[] fields = csvParser.ReadFields();
int rowNums = fields.Length;
for(int index = 0; index < rowNums;index++) {
pointerX = (index + 1) * offsetX;
string name = fields[index];
TextBox n = new TextBox() { Text = name, Location = new Point(pointerX, pointerY) };
panel2.Controls.Add(n);
panel2.Show(); // should be unnecessary
}
}
}

How to set cell color programmatically epplus?

I was wondering if it is possible to set cell color programmatically using epplus?
I load my data from a sql stored procedure and it works well, but my users want
cells that contain the words 'Annual Leave' to have a background color of light yellow instead of the default white. Is there a way to do this? perhaps by iterating through a datatable perhaps? Below is where
public void ExportTableData(DataTable dtdata)
{
//Using EPPLUS to export Spreadsheets
ExcelPackage pck = new ExcelPackage();
var ws = pck.Workbook.Worksheets.Add("Availability list");
ws.Cells["A1"].LoadFromDataTable(dtdata, true);
ws.Cells["A1:G1"].Style.Font.Bold = true;
ws.Cells["A1:G1"].Style.Font.UnderLine = true;
//change cell color depending on the text input from stored proc?
if (dtdata.Rows[4].ToString() == "Annual Leave")
{
ws.Cells["E1"].Style.Fill.PatternType = ExcelFillStyle.Solid;
ws.Cells["E1"].Style.Fill.BackgroundColor.SetColor(Color.LightYellow);
}
pck.SaveAs(Response.OutputStream);
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", "attachment; filename=Availability.xlsx");
Response.End();
}
Check your line:
if (dtdata.Rows[4].ToString() == "Annual Leave")
If it is a standard .net table wouldnt .ToString() evaluate to "System.Data.DataRow"? Also ws.Cells["E1"] will need to be adjusted for each cell after looping through the row count (basically what krillgar was saying).
Something like that:
[TestMethod]
public void Cell_Color_Background_Test()
{
//http://stackoverflow.com/questions/28679602/how-to-set-cell-color-programmatically-epplus
//Throw in some data
var dtdata = new DataTable("tblData");
dtdata.Columns.Add(new DataColumn("Col1", typeof(string)));
dtdata.Columns.Add(new DataColumn("Col2", typeof(int)));
dtdata.Columns.Add(new DataColumn("Col3", typeof(int)));
for (var i = 0; i < 20; i++)
{
var row = dtdata.NewRow();
row["Col1"] = "Available";
row["Col2"] = i * 10;
row["Col3"] = i * 100;
dtdata.Rows.Add(row);
}
//throw in one cell that triggers
dtdata.Rows[10]["Col1"] = "Annual leave";
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
if (existingFile.Exists)
existingFile.Delete();
using (var pck = new ExcelPackage(existingFile))
{
//Using EPPLUS to export Spreadsheets
var ws = pck.Workbook.Worksheets.Add("Availability list");
ws.Cells["A1"].LoadFromDataTable(dtdata, true);
ws.Cells["A1:G1"].Style.Font.Bold = true;
ws.Cells["A1:G1"].Style.Font.UnderLine = true;
//change cell color depending on the text input from stored proc?
//if (dtdata.Rows[4].ToString() == "Annual Leave")
for (var i = 0; i < dtdata.Rows.Count; i++)
{
if (dtdata.Rows[i]["Col1"].ToString() == "Annual leave")
{
ws.Cells[i + 1, 1].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 1, 1].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightYellow);
}
}
pck.Save();
}
Thanks Ernie! I changed it slightly to allow for my header in excel and to also to make sure that the code doesnt start at E1. I used ws.cells[i + 2, 5] to do this. Cheers!
for (var i = 0; i < dtdata.Rows.Count; i++)
{
if (dtdata.Rows[i]["typeName"].ToString() == "Annual Leave")
{
ws.Cells[i + 2, 5].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 2, 5].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightYellow);
}
else if (dtdata.Rows[i]["typeName"].ToString() == "Available")
{
ws.Cells[i + 2, 5].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 2, 5].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGreen);
}
else
{
ws.Cells[i + 2, 5].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 2, 5].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.White);
}
}
You could try using the conditional formatting option in EPPlus
here is their sample
and here is a SO post with someone else who has used this option
Generally using links as answers is not my style but no reason to remake those wheels, if the EPP sample goes away im guessing so did they and if the SO sample is gone then im guessing so with this answer.
Hope it helps

Excel file with openxml with multiple sheets in single workbook

I am trying to insert data to multiple sheets. For my excel, I have two sheets which are "charts" and "ChartData". I'm able to update the data in sheet2, chartdata sheet, but I'm unable to insert data to sheet1. Here is code which I have tried to insert data into excel sheets. Here data is coming from database.
double ticks = DateTime.Now.Ticks;
// MarketAnalysis ms = new MarketAnalysis();
//ms.Marketanalysis();
File.Copy(Srcpath, #"E:\Works\OpenXML\DownloadTemplates\ExcelGenerated" + ticks + ".xlsx", true);
using (SpreadsheetDocument myworkbok = SpreadsheetDocument.Open(#"E:\Works\OpenXML\DownloadTemplates\ExcelGenerated" + ticks + ".xlsx", true))
{
//Acess the main workbook which contain all the references
WorkbookPart workbookpart = myworkbok.WorkbookPart;
//Get sheet by name
Sheet sheet = workbookpart.Workbook.Descendants<Sheet>().Where(s => s.Name == "ChartData").FirstOrDefault();
//Worksheet Part by ID
WorksheetPart worksheetpart = workbookpart.GetPartById(sheet.Id) as WorksheetPart;
//Sheet data contains all the data
SheetData sheetdata = worksheetpart.Worksheet.GetFirstChild<SheetData>();
DataSet ds = db.Chart1Data();
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
// if (ds.Tables[0].Rows !=DBNull)
//{
string Rowlabel = ds.Tables[0].Rows[i][0].ToString();
// float? FY13Actuval = Convert.ToInt32(ds.Tables[0].Rows[i][1]);
if (!string.IsNullOrEmpty(ds.Tables[0].Rows[i][1].ToString()))
{
// string s = ds.Tables[0].Rows[i][1].ToString();
//FY13Actuval=float.Parse(ds.Tables[0].Rows[i][1].ToString());
FY13Actuval = Convert.ToDouble(ds.Tables[0].Rows[i][1].ToString());
}
else
{
FY13Actuval = null;
}
double? Budget = Convert.ToDouble(ds.Tables[0].Rows[i][2].ToString());
double? Actuval = Convert.ToDouble(ds.Tables[0].Rows[i][3].ToString());
Row contentrow = CreateContentRow(index, Product, Actual, Budget, Forecast);
index++;
sheetdata.AppendChild(contentrow);
// }
}
.......Same code for the other 3 charts
Sheet sheet1 = workbookpart.Workbook.Descendants<Sheet>().Where(s => s.Name == "Charts").FirstOrDefault();
WorksheetPart worksheetpart1 = workbookpart.GetPartById(sheet1.Id) as WorksheetPart;
//Sheet data contains all the data
SheetData sheetdata1 = worksheetpart1.Worksheet.GetFirstChild<SheetData>();
DataSet dsTbl = db.Table();
int SCMTblIndex=5;
for (int i = 0; i < dsTbl.Tables[0].Rows.Count; i++)
{
Row tblRow = CreateScorecardMetricTblRow(SCMTblIndex, dsTbl.Tables[0].Rows[i][0].ToString(),
dsTbl.Tables[0].Rows[i][1].ToString(),
dsTbl.Tables[0].Rows[i][2].ToString());
SCMTblIndex++;
sheetdata1.AppendChild(tblRow);
}
myworkbok.WorkbookPart.Workbook.Save();
}
and the Methods to create the cells:
public static string[] headerColumns = new string[]{ "A", "B", "C", "D", "E", "F", "G", "I", "J" };
public static string[] header = new string[] { "D", "E", "F" };
private static Row CreateContentRow(int index, string Product, double? Actual, double? Budget, double? Forecast)
{
//Create New ROw
Row r = new Row();
r.RowIndex = (UInt32)index;
//Begin colums
Cell c0 = new Cell();
c0.CellReference = headerColumns[0] + index;
CellValue v0 = new CellValue();
v0.Text = Product;
c0.AppendChild(v0);
r.AppendChild(c0);
Cell c1 = new Cell();
c1.CellReference = headerColumns[1] + index;
CellValue v1 = new CellValue();
v1.Text = Actual.ToString();
c1.AppendChild(v1);
r.AppendChild(c1);
Cell c2 = new Cell();
c2.CellReference = headerColumns[2] + index;
CellValue v2 = new CellValue();
v2.Text = Budget.ToString();
c2.AppendChild(v2);
r.AppendChild(c2);
Cell c3 = new Cell();
c3.CellReference = headerColumns[3] + index;
CellValue v3 = new CellValue();
v3.Text = Forecast.ToString();
c3.AppendChild(v3);
r.AppendChild(c3);
return r;
}
public static Row CreateScorecardMetricTblRow(int index, string Act_Data, string Bud_Data, string VarPr_Data)
{
Row r = new Row();
r.RowIndex = (UInt32)index;
//Begin Colums
Cell c0 = new Cell();
c0.CellReference = header[0] + index;
CellValue v0 = new CellValue();
v0.Text = Act_Data;
c0.AppendChild(v0);
r.AppendChild(c0);
Cell c1 = new Cell();
c1.CellReference = header[1] + index;
CellValue v1 = new CellValue();
v1.Text = Bud_Data;
c1.AppendChild(v1);
r.AppendChild(c1);
Cell c2 = new Cell();
c2.CellReference = header[2] + index;
CellValue v2 = new CellValue();
v2.Text = VarPr_Data;
c2.AppendChild(v2);
r.AppendChild(c2);
return r;
}
for multiple sheets to updated just
// Open the document for editing.
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(docName, true))
{
// Insert code here.
}
create a two classes for two sheets which you need to update sheet cell values.For each class constructor pass the SpreadsheetDocument object and implement insert text into a cell in a spreadsheet document.Just avoid the code for insertion of new sheet and try the insertion of the cell code only.
You can try this method.....it may be helpful..
private static Cell InsertCellInWorksheet(string columnName, int rowIndex, WorksheetPart worksheetPart)
{
Worksheet worksheet = worksheetPart.Worksheet;
SheetData sheetData = worksheet.GetFirstChild<SheetData>();
string cellReference = columnName + rowIndex;
Alignment alignment1 = new Alignment() { WrapText = true };
// If the worksheet does not contain a row with the specified row index, insert one.
Row row;
row = new Row() { RowIndex = 3, StyleIndex = 1 };
if (sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).Count() != 0)
{
row = sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).First();
}
else
{
row = new Row() { RowIndex = Convert.ToUInt32(rowIndex) };
sheetData.Append(row);
}
// If there is not a cell with the specified column name, insert one.
if (row.Elements<Cell>().Where(c => c.CellReference.Value == columnName + rowIndex).Count() > 0)
{
Row row2;
row2 = new Row() { RowIndex = 3, StyleIndex = 1 };
Cell rCell = null;
foreach (Cell celld in row.Elements<Cell>())
{
if (string.Compare(celld.CellReference.Value, "A3", true) > 0)
{
rCell = celld;
break;
}
}
// Add the cell to the cell table at A1.
Cell newCell = new Cell() { CellReference = "C3" };
//Cell newCell1 = new Cell() { CellReference = "D3" };
row.InsertBefore(newCell, rCell);
//row.InsertBefore(newCell1, rCell);
// Set the cell value to be a numeric value of 100.
newCell.CellValue = new CellValue("#GUIDeXactLCMS#");
//newCell1.CellValue = new CellValue("EN");
newCell.DataType = new EnumValue<CellValues>(CellValues.Number);
return row.Elements<Cell>().Where(c => c.CellReference.Value == cellReference).First();
}
else
{
// Cells must be in sequential order according to CellReference. Determine where to insert the new cell.
Cell refCell = null;
foreach (Cell cell in row.Elements<Cell>())
{
if (string.Compare(cell.CellReference.Value, cellReference, true) > 0)
{
//string val = cell.CellReference.Value;
refCell = cell;
break;
}
}
Cell newCell = new Cell() { CellReference = cellReference };
row.InsertBefore(newCell, refCell);
return newCell;
}
worksheet.Save();
}

Excel to DataTable using EPPlus - excel locked for editing

I'm using the following code to convert an Excel to a datatable using EPPlus:
public DataTable ExcelToDataTable(string path)
{
var pck = new OfficeOpenXml.ExcelPackage();
pck.Load(File.OpenRead(path));
var ws = pck.Workbook.Worksheets.First();
DataTable tbl = new DataTable();
bool hasHeader = true;
foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column])
{
tbl.Columns.Add(hasHeader ? firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column));
}
var startRow = hasHeader ? 2 : 1;
for (var rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++)
{
var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column];
var row = tbl.NewRow();
foreach (var cell in wsRow)
{
row[cell.Start.Column - 1] = cell.Text;
}
tbl.Rows.Add(row);
}
pck.Dispose();
return tbl;
}
It creates the Excel, however, when I try to open it, it gives me the message that it is locked for editing by another user and that I can only open it in Read-Only mode.
I thought using:
pck.Dispose();
would solve the issue, however I'm still getting the same error.
Also, when I try to delete the file, I get the message: The action can't be completed because the file is open in WebDev.WebServer40.EXE.
Any ideas how to resolve this?
Thanks in advance. :)
I see, that's what i've posted recently here(now corrected). It can be improved since the ExcelPackage and the FileStream(from File.OpenRead) are not disposed after using.
public static DataTable GetDataTableFromExcel(string path, bool hasHeader = true)
{
using (var pck = new OfficeOpenXml.ExcelPackage())
{
using (var stream = File.OpenRead(path))
{
pck.Load(stream);
}
var ws = pck.Workbook.Worksheets.First();
DataTable tbl = new DataTable();
foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column])
{
tbl.Columns.Add(hasHeader ? firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column));
}
var startRow = hasHeader ? 2 : 1;
for (int rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++)
{
var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column];
DataRow row = tbl.Rows.Add();
foreach (var cell in wsRow)
{
row[cell.Start.Column - 1] = cell.Text;
}
}
return tbl;
}
}
A extension version of Tim Schmelter's answer.
public static DataTable ToDataTable(this ExcelWorksheet ws, bool hasHeaderRow = true)
{
var tbl = new DataTable();
foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column])
tbl.Columns.Add(hasHeaderRow ?
firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column));
var startRow = hasHeaderRow ? 2 : 1;
for (var rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++)
{
var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column];
var row = tbl.NewRow();
foreach (var cell in wsRow) row[cell.Start.Column - 1] = cell.Text;
tbl.Rows.Add(row);
}
return tbl;
}
I've created a method that converts an Excel file to a DataTable using EPPlus, and tried to maintain Type Safety. Also duplicate column names are handled and with a boolean you can tell the method wether the sheet has a row with headers. I've created it for a complex import process that has several steps after uploading that requires user input before committing to the database.
private DataTable ExcelToDataTable(byte[] excelDocumentAsBytes, bool hasHeaderRow)
{
DataTable dt = new DataTable();
string errorMessages = "";
//create a new Excel package in a memorystream
using (MemoryStream stream = new MemoryStream(excelDocumentAsBytes))
using (ExcelPackage excelPackage = new ExcelPackage(stream))
{
ExcelWorksheet worksheet = excelPackage.Workbook.Worksheets[1];
//check if the worksheet is completely empty
if (worksheet.Dimension == null)
{
return dt;
}
//add the columns to the datatable
for (int j = worksheet.Dimension.Start.Column; j <= worksheet.Dimension.End.Column; j++)
{
string columnName = "Column " + j;
var excelCell = worksheet.Cells[1, j].Value;
if (excelCell != null)
{
var excelCellDataType = excelCell;
//if there is a headerrow, set the next cell for the datatype and set the column name
if (hasHeaderRow == true)
{
excelCellDataType = worksheet.Cells[2, j].Value;
columnName = excelCell.ToString();
//check if the column name already exists in the datatable, if so make a unique name
if (dt.Columns.Contains(columnName) == true)
{
columnName = columnName + "_" + j;
}
}
//try to determine the datatype for the column (by looking at the next column if there is a header row)
if (excelCellDataType is DateTime)
{
dt.Columns.Add(columnName, typeof(DateTime));
}
else if (excelCellDataType is Boolean)
{
dt.Columns.Add(columnName, typeof(Boolean));
}
else if (excelCellDataType is Double)
{
//determine if the value is a decimal or int by looking for a decimal separator
//not the cleanest of solutions but it works since excel always gives a double
if (excelCellDataType.ToString().Contains(".") || excelCellDataType.ToString().Contains(","))
{
dt.Columns.Add(columnName, typeof(Decimal));
}
else
{
dt.Columns.Add(columnName, typeof(Int64));
}
}
else
{
dt.Columns.Add(columnName, typeof(String));
}
}
else
{
dt.Columns.Add(columnName, typeof(String));
}
}
//start adding data the datatable here by looping all rows and columns
for (int i = worksheet.Dimension.Start.Row + Convert.ToInt32(hasHeaderRow); i <= worksheet.Dimension.End.Row; i++)
{
//create a new datatable row
DataRow row = dt.NewRow();
//loop all columns
for (int j = worksheet.Dimension.Start.Column; j <= worksheet.Dimension.End.Column; j++)
{
var excelCell = worksheet.Cells[i, j].Value;
//add cell value to the datatable
if (excelCell != null)
{
try
{
row[j - 1] = excelCell;
}
catch
{
errorMessages += "Row " + (i - 1) + ", Column " + j + ". Invalid " + dt.Columns[j - 1].DataType.ToString().Replace("System.", "") + " value: " + excelCell.ToString() + "<br>";
}
}
}
//add the new row to the datatable
dt.Rows.Add(row);
}
}
//show error messages if needed
Label1.Text = errorMessages;
return dt;
}
The webforms button click for demo purposes.
protected void Button1_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
DataTable dt = ExcelToDataTable(FileUpload1.FileBytes, CheckBox1.Checked);
GridView1.DataSource = dt;
GridView1.DataBind();
}
}
VDWWD's answer above works great to keep type safety, and I built upon it with some improvements.
Method reads from a file directly.
Column type detection by using all rows and not just one value. Column type is set to String if more than type is found in the column.
Error Messages returned in a list of strings.
Here is the updated version:
public static DataTable ExcelToDataTable(string path, ref List<string> errorList, bool hasHeaderRow = true )
{
DataTable dt = new DataTable();
errorList = new List<string>();
//create a new Excel package
using (ExcelPackage excelPackage = new ExcelPackage())
{
using (var stream = File.OpenRead(path))
{
excelPackage.Load(stream);
}
ExcelWorksheet worksheet = excelPackage.Workbook.Worksheets[1];
//check if the worksheet is completely empty
if (worksheet.Dimension == null)
{
return dt;
}
//add the columns to the datatable
for (int j = worksheet.Dimension.Start.Column; j <= worksheet.Dimension.End.Column; j++)
{
string columnName = "Column " + j;
//Build hashset with all types in the row
var columnTypes = new HashSet<Type>();
for (int i = worksheet.Dimension.Start.Row + Convert.ToInt32(hasHeaderRow); i <= worksheet.Dimension.End.Row; i++)
{
//Only add type if cell value not empty
if (worksheet.Cells[i, j].Value != null)
{
columnTypes.Add(worksheet.Cells[i, j].Value.GetType());
}
}
var excelCell = worksheet.Cells[1, j].Value;
if (excelCell != null)
{
Type excelCellDataType = null;
//if there is a headerrow, set the next cell for the datatype and set the column name
if (hasHeaderRow == true)
{
columnName = excelCell.ToString();
//check if the column name already exists in the datatable, if so make a unique name
if (dt.Columns.Contains(columnName) == true)
{
columnName = columnName + "_" + j;
}
}
//Select input type for the column
if (columnTypes.Count == 1)
{
excelCellDataType = columnTypes.First();
}
else
{
excelCellDataType = typeof(string);
}
//try to determine the datatype for the column (by looking at the next column if there is a header row)
if (excelCellDataType == typeof(DateTime))
{
dt.Columns.Add(columnName, typeof(DateTime));
}
else if (excelCellDataType == typeof(Boolean))
{
dt.Columns.Add(columnName, typeof(Boolean));
}
else if (excelCellDataType == typeof(Double))
{
//determine if the value is a decimal or int by looking for a decimal separator
//not the cleanest of solutions but it works since excel always gives a double
if (excelCellDataType.ToString().Contains(".") || excelCellDataType.ToString().Contains(","))
{
dt.Columns.Add(columnName, typeof(Decimal));
}
else
{
dt.Columns.Add(columnName, typeof(Int64));
}
}
else
{
dt.Columns.Add(columnName, typeof(String));
}
}
else
{
dt.Columns.Add(columnName, typeof(String));
}
}
//start adding data the datatable here by looping all rows and columns
for (int i = worksheet.Dimension.Start.Row + Convert.ToInt32(hasHeaderRow); i <= worksheet.Dimension.End.Row; i++)
{
//create a new datatable row
DataRow row = dt.NewRow();
//loop all columns
for (int j = worksheet.Dimension.Start.Column; j <= worksheet.Dimension.End.Column; j++)
{
var excelCell = worksheet.Cells[i, j].Value;
//add cell value to the datatable
if (excelCell != null)
{
try
{
row[j - 1] = excelCell;
}
catch
{
errorList.Add("Row " + (i - 1) + ", Column " + j + ". Invalid " + dt.Columns[j - 1].DataType.ToString().Replace("System.", "") + " value: " + excelCell.ToString() );
}
}
}
//add the new row to the datatable
dt.Rows.Add(row);
}
}
return dt;
}
This is an improvement to the generic one above. Use is if you have a class with the following properties, "Name", "Surname", "Telephone", "Fax" and you have a excel sheet with the first row with the same names, it will load the excel rows into a class object and pop it into a List
public static List<T> GetClassFromExcel<T>(string path, int fromRow, int fromColumn, int toRow = 0, int toColumn = 0)
{
if (toColumn != 0 && toColumn < fromColumn) throw new Exception("toColumn can not be less than fromColumn");
if (toRow != 0 && toRow < fromRow) throw new Exception("toRow can not be less than fromRow");
List<T> retList = new List<T>();
using (var pck = new ExcelPackage())
{
using (var stream = File.OpenRead(path))
{
pck.Load(stream);
}
//Retrieve first Worksheet
var ws = pck.Workbook.Worksheets.First();
//If the to column is empty or 0, then make the tocolumn to the count of the properties
//Of the class object inserted
toColumn = toColumn == 0 ? typeof(T).GetProperties().Count() : toColumn;
//Read the first Row for the column names and place into a list so that
//it can be used as reference to properties
Dictionary<string, int> columnNames = new Dictionary<string, int>();
// wsRow = ws.Row(0);
var colPosition = 0;
foreach (var cell in ws.Cells[1, 1, 1, toColumn == 0 ? ws.Dimension.Columns : toColumn])
{
columnNames.Add(cell.Value.ToString(), colPosition);
colPosition++;
}
//create a instance of T
T objT = Activator.CreateInstance<T>();
//Retrieve the type of T
Type myType = typeof(T);
//Get all the properties associated with T
PropertyInfo[] myProp = myType.GetProperties();
//Loop through the rows of the excel sheet
for (var rowNum = fromRow; rowNum <= (toRow == 0? ws.Dimension.End.Row : toRow); rowNum++)
{
var wsRow = ws.Cells[rowNum, fromColumn, rowNum, ws.Cells.Count()];
foreach (var propertyInfo in myProp)
{
if (columnNames.ContainsKey(propertyInfo.Name))
{
int position = 0;
columnNames.TryGetValue(propertyInfo.Name, out position);
//int position = columnNames.IndexOf(propertyInfo.Name);
//To prevent an exception cast the value to the type of the property.
propertyInfo.SetValue(objT, Convert.ChangeType(wsRow[rowNum, position + 1].Value, propertyInfo.PropertyType));
}
}
retList.Add(objT);
}
}
return retList;
}
now you can use the list as a databinding source if you need...
A give from me to you... :) Daniel C. Vrey
Updated it for toColumn to work and added toRow and followed Andreas suggestions. Thumbs up for Andreas
public static List<T> getClassFromExcel<T>(string path, int fromRow, int fromColumn, int toColumn = 0) where T : class
{
using (var pck = new OfficeOpenXml.ExcelPackage())
{
List<T> retList = new List<T>();
using (var stream = File.OpenRead(path))
{
pck.Load(stream);
}
var ws = pck.Workbook.Worksheets.First();
toColumn = toColumn == 0 ? typeof(T).GetProperties().Count() : toColumn;
for (var rowNum = fromRow; rowNum <= ws.Dimension.End.Row; rowNum++)
{
T objT = Activator.CreateInstance<T>();
Type myType = typeof(T);
PropertyInfo[] myProp = myType.GetProperties();
var wsRow = ws.Cells[rowNum, fromColumn, rowNum, toColumn];
for (int i = 0; i < myProp.Count(); i++)
{
myProp[i].SetValue(objT, wsRow[rowNum, fromColumn + i].Text);
}
retList.Add(objT);
}
return retList;
}
}
public static List<T> GetClassFromExcel<T>(string path, int fromRow, int fromColumn, int toRow = 0, int toColumn = 0) where T: class, new()
{
if (toColumn != 0 && toColumn < fromColumn) throw new Exception("toColumn can not be less than fromColumn");
if (toRow != 0 && toRow < fromRow) throw new Exception("toRow can not be less than fromRow");
List<T> retList = new List<T>();
using (var pck = new ExcelPackage())
{
using (var stream = File.OpenRead(path))
{
pck.Load(stream);
}
//Retrieve first Worksheet
var ws = pck.Workbook.Worksheets.First();
toColumn = toColumn == 0 ? typeof(T).GetProperties().Count() : toColumn; //If the to column is empty or 0, then make the tocolumn to the count of the properties Of the class object inserted
//Read the first Row for the column names and place into a list so that
//it can be used as reference to properties
Dictionary<string, int> columnNames = new Dictionary<string, int>();
// wsRow = ws.Row(0);
var colPosition = 0;
foreach (var cell in ws.Cells[1, 1, 1, toColumn == 0 ? ws.Dimension.Columns : toColumn])
{
columnNames.Add(cell.Value.ToString(), colPosition);
colPosition++;
}
//Retrieve the type of T
Type myType = typeof(T);
//Get all the properties associated with T
PropertyInfo[] myProp = myType.GetProperties();
//Loop through the rows of the excel sheet
for (var rowNum = fromRow + 1; rowNum <= (toRow == 0 ? ws.Dimension.End.Row : toRow); rowNum++) // fromRow + 1 to read from next row after columnheader
{
//create a instance of T
//T objT = Activator.CreateInstance<T>();
T objT = new T();
// var wsRow = ws.Cells[rowNum, fromColumn, rowNum, ws.Cells.Count()]; //ws.Cells.Count() causing out of range error hence using ws.Dimension.Columns to get last column index
var wsRow = ws.Cells[rowNum, fromColumn, rowNum, ws.Dimension.Columns];
foreach (var propertyInfo in myProp)
{
var attribute = propertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true).Cast<DisplayNameAttribute>().SingleOrDefault();
string displayName = attribute != null && !string.IsNullOrEmpty(attribute.DisplayName) ? attribute.DisplayName : propertyInfo.Name; // If DisplayName annotation not used then get property name itself
if (columnNames.ContainsKey(displayName))
{
int position = 0;
columnNames.TryGetValue(displayName, out position);
////int position = columnNames.IndexOf(propertyInfo.Name);
////To prevent an exception cast the value to the type of the property.
propertyInfo.SetValue(objT, Convert.ChangeType(wsRow[rowNum, position + 1].Value, propertyInfo.PropertyType));
}
}
retList.Add(objT);
}
}
return retList;
}
//IMPLEMENTATION DONE BY PLACING Code IT IN SEPARATE Helpers.CS file and
//Consuming it in this manner
List<CustomerExcelModel> records =
Helpers.GetClassFromExcel<CustomerExcelModel>(filelocation, 1, 1);
Thanks a lot to the user who Submitted code and Andreas for suggestion
Here are the Following changes done, i am new to generics so forgive and correct me for any mistakes please find modified code below it might help someone
Added Display Annotation entity model to map with the Excel Column
name so that Column Name with spaces can also be handled.
had issue "T objT " as it was outside of for loop and hence caused
same value repeatedly inserted into List fixed it by
instantiating inside loop i.e using "new T()"
Fixed Column out of range error by using "ws.Dimension.Columns" to get Column count , instead of ws.Cells.Count() as it caused out
range column error
for looping through row data added +1 to it ,as RowNum=1 was reading header name also so done minor change of "rowNum = fromRow + 1"
Here I am sharing how you can read the excel. You can modify it to store each date in datatables.
public void readXLS(string FilePath)
{
FileInfo existingFile = new FileInfo(FilePath);
using (ExcelPackage package = new ExcelPackage(existingFile))
{
//get the first worksheet in the workbook
ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
int colCount = worksheet.Dimension.End.Column; //get Column Count
int rowCount = worksheet.Dimension.End.Row; //get row count
for (int row = 1; row <= rowCount; row++)
{
for (int col = 1; col <= colCount; col++)
{
//You can update code here to add each cell value to DataTable.
Console.WriteLine(" Row:" + row + " column:" + col + " Value:" + worksheet.Cells[row, col].Value.ToString().Trim());
}
}
}
}
Reff: http://sforsuresh.in/read-data-excel-sheet-insert-database-table-c/
Use below code if you want to read data of each worksheet from excel, as well if worksheet contain date format data for particular column.
public static DataSet ReadExcelFileToDataSet2(string filePath, bool isFirstRowHeader=true)
{
DataSet result = new DataSet();
Excel.ExcelPackage xlsPackage = new Excel.ExcelPackage(new FileInfo(filePath)); //using Excel = OfficeOpenXml; <--EPPLUS
Excel.ExcelWorkbook workBook = xlsPackage.Workbook;
try
{
for (int count = 1; count <= workBook.Worksheets.Count; count++)
{
Excel.ExcelWorksheet wsworkSheet = workBook.Worksheets[count];
if (wsworkSheet.Name.ToLower() == "sheetName")
{
wsworkSheet.Column(4).Style.Numberformat.Format = "MM-dd-yyyy"; // set column value to read as Date Type or numberformat
}
DataTable tbl = new DataTable();
// wsworkSheet.Dimension - (It will return cell dimesion like A1:N7 , means returning the worksheet dimesions.)
// wsworkSheet.Dimension.End.Address - (It will return right bottom cell like N7)
// wsworkSheet.Dimension.End.Columns - (It will return count from A1 to N7 like here 14)
foreach (var firstRowCell in wsworkSheet.Cells[1, 1, 1, wsworkSheet.Dimension.End.Column]) //.Cells[Row start, Column Start, Row end, Column End]
{
var colName = "";
colName = firstRowCell.Text;
tbl.Columns.Add(isFirstRowHeader ? colName : string.Format("Column {0}", firstRowCell.Start.Column)); //Geth the Column index (index starting with 1) from the left top.
}
var startRow = isFirstRowHeader ? 2 : 1;
for (int rowNum = startRow; rowNum <= wsworkSheet.Dimension.End.Row; rowNum++)
{
var wsRow = wsworkSheet.Cells[rowNum, 1, rowNum, wsworkSheet.Dimension.End.Column]; // wsworkSheet.Cells[Row start, Column Start, Row end, Column End]
DataRow row = tbl.Rows.Add();
foreach (var cell in wsRow)
{
row[cell.Start.Column - 1] = cell.Text;
}
}
tbl.TableName = wsworkSheet.Name;
result.Tables.Add(tbl);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return result;
}

Categories

Resources