I would like to read the contents of an Excel worksheet into a C# DataTable. The Excel worksheet could have a variable numbers of columns and rows. The first row in the Excel worksheet will always contain the column names but other rows may be blank.
All of the suggestions I have seen here in SO all assume the presence of Microsoft.ACE.OLEDB. I do not have this library installed on my system as when I try some of these solutions I get this error.
Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine.
Strange considering I have Office 2016 installed.
For this reason I was hoping to use the ClosedXML library via Nuget but I do not see any examples in their wiki of reading an Excel worksheet to a DataTable in C#.
This is example is not mine. I cannot remember where I got it from as it was in my archives. However, this works for me. The only issue I ran into was with blank cells. According to a dicussion on the ClosedXML GitHUb wiki page it has something to do with Excel not tracking empty cells that are not bounded by data. I found that if I added data to the cells and then removed the same data the process worked.
public static DataTable ImportExceltoDatatable(string filePath, string sheetName)
{
// Open the Excel file using ClosedXML.
// Keep in mind the Excel file cannot be open when trying to read it
using (XLWorkbook workBook = new XLWorkbook(filePath))
{
//Read the first Sheet from Excel file.
IXLWorksheet workSheet = workBook.Worksheet(1);
//Create a new DataTable.
DataTable dt = new DataTable();
//Loop through the Worksheet rows.
bool firstRow = true;
foreach (IXLRow row in workSheet.Rows())
{
//Use the first row to add columns to DataTable.
if (firstRow)
{
foreach (IXLCell cell in row.Cells())
{
dt.Columns.Add(cell.Value.ToString());
}
firstRow = false;
}
else
{
//Add rows to DataTable.
dt.Rows.Add();
int i = 0;
foreach (IXLCell cell in row.Cells(row.FirstCellUsed().Address.ColumnNumber, row.LastCellUsed().Address.ColumnNumber))
{
dt.Rows[dt.Rows.Count - 1][i] = cell.Value.ToString();
i++;
}
}
}
return dt;
}
}
Need to add
using System.Data;
using ClosedXML.Excel;
As well as the ClosedXML nuget package
For other datetime data type... this could be helpful... reference
if (cell.Address.ColumnLetter=="J") // Column with date datatype
{
DateTime dtime = DateTime.FromOADate(double.Parse(cell.Value.ToString()));
dt.Rows[dt.Rows.Count - 1][i] = dtime;
}
else
{
dt.Rows[dt.Rows.Count - 1][i] = cell.Value.ToString();
}
With this code you can read the contents of an excel sheet. You can specify the name of the sheet or the number, a dataSet will be returned with the contents of the sheet.
public static DataTable GetDataFromExcel(string path, dynamic worksheet)
{
//Save the uploaded Excel file.
DataTable dt = new DataTable();
//Open the Excel file using ClosedXML.
using (XLWorkbook workBook = new XLWorkbook(path))
{
//Read the first Sheet from Excel file.
IXLWorksheet workSheet = workBook.Worksheet(worksheet);
//Create a new DataTable.
//Loop through the Worksheet rows.
bool firstRow = true;
foreach (IXLRow row in workSheet.Rows())
{
//Use the first row to add columns to DataTable.
if (firstRow)
{
foreach (IXLCell cell in row.Cells())
{
if (!string.IsNullOrEmpty(cell.Value.ToString()))
{
dt.Columns.Add(cell.Value.ToString());
}
else
{
break;
}
}
firstRow = false;
}
else
{
int i = 0;
DataRow toInsert = dt.NewRow();
foreach (IXLCell cell in row.Cells(1, dt.Columns.Count))
{
try
{
toInsert[i] = cell.Value.ToString();
}
catch (Exception ex)
{
}
i++;
}
dt.Rows.Add(toInsert);
}
}
return dt;
}
Related
I'm trying to get the hyperlink string from an Excel cell using the excel data reader library in C#, but retrieving the cell and calling the ToString method didn't work.
This is my code so far:
using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read)){
using var reader = ExcelReaderFactory.CreateReader(stream);
var result = reader.AsDataSet();
DataTable table = result.Tables[0];
bool jumpCol = true; // Avoid first lane
foreach (DataRow row in table.Rows)
{
if (jumpCol)
{
jumpCol = false;
continue;
}
}
PlateImage = row[6].ToString(); //This returns 0
I just need the hyperlink string:
=HYPERLINK("thisString.jpg")
Any ideas? Any library that allows me to do this?
Install Free Spire.XLS from NuGet:
Install-Package FreeSpire.XLS
Next, you can read the hyperlinks from Excel with the following code:
using Spire.Xls;
using System;
namespace RetrieveHyperlink
{
class Program
{
static void Main(string[] args)
{
Workbook wb = new Workbook();
wb.LoadFromFile(#"C:\Users\Administrator\Desktop\Hyperlinks.xlsx");
Worksheet sheet = wb.Worksheets[0];
foreach (var item in sheet.HyperLinks)
{
string address = item.Address;
CellRange range = item.Range;
Console.WriteLine(string.Format("Cell[{0},{1}] contains URL: {2}", range.Row, range.Column, address));
}
}
}
}
Keep in mind that the free version has a limitation of 5 sheets and 200 rows per sheet for .xls file format.
I have a situation where I need to download and save excel file(.xlsx) to .CSV format in .net core console application.
Since, Microsoft.Interop packages are not compatible with .Net core 3.1, what other approach I can use to save Excel file as .CSV?
Appreciate suggestions.
This is a combination of multiple existing answers on SO.
First is from here
Convert the xlsx to a DataTable using ClosedXML
using ClosedXML.Excel;
...
public static DataTable GetDataFromExcel(string path, dynamic worksheet)
{
//Save the uploaded Excel file.
DataTable dt = new DataTable();
//Open the Excel file using ClosedXML.
using (XLWorkbook workBook = new XLWorkbook(path))
{
//Read the first Sheet from Excel file.
IXLWorksheet workSheet = workBook.Worksheet(worksheet);
//Create a new DataTable.
//Loop through the Worksheet rows.
bool firstRow = true;
foreach (IXLRow row in workSheet.Rows())
{
//Use the first row to add columns to DataTable.
if (firstRow)
{
foreach (IXLCell cell in row.Cells())
{
if (!string.IsNullOrEmpty(cell.Value.ToString()))
{
dt.Columns.Add(cell.Value.ToString());
}
else
{
break;
}
}
firstRow = false;
}
else
{
int i = 0;
DataRow toInsert = dt.NewRow();
foreach (IXLCell cell in row.Cells(1, dt.Columns.Count))
{
try
{
toInsert[i] = cell.Value.ToString();
}
catch (Exception ex)
{
//Handle this, or don't.
}
i++;
}
dt.Rows.Add(toInsert);
}
}
return dt;
}
If you need to do any data transformations, do it while the data is in a DataTable.
Then use CSVHelper to export as a CSV (SO answer I found had a solution that didn't use the Culture Info which was added as a requirement to the Library a few updates ago):
using CSVHelper;
using System.Globilization;
....
public static void SaveCSV(DataTable records)
{
string newFile = #"C:\somePath.csv";
using (StreamWriter writer = new StreamWriter(newFile))
{
using (CsvWriter csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//add headers
foreach (DataColumn dc in records.Columns)
{
csv.WriteField(dc.ColumnName);
}
csv.NextRecord();
foreach(DataRow dr in records.Rows)
{
for (int i = 0; i< records.Columns.Count; i++)
{
csv.WriteField(dr[i]);
}
csv.NextRecord();
}
}
}
}
I want to load an Excel-file into my DataGrid, using ClosedXML.
I have this method:
public static DataTable ImportExceltoDataTable(string filePath, string sheetName) {
using (XLWorkbook wb = new(filePath)) {
IXLWorksheet ws = wb.Worksheet(1);
DataTable dt = new();
bool firstRow = true;
foreach (IXLRow row in ws.Rows()) {
if (firstRow) {
foreach (IXLCell cell in row.Cells()) {
dt.Columns.Add(cell.CachedValue.ToString());
}
firstRow = false;
} else {
dt.Rows.Add();
int i = 0;
foreach (IXLCell cell in row.Cells(row.FirstCellUsed().Address.ColumnNumber, row.LastCellUsed().Address.ColumnNumber)) {
dt.Rows[dt.Rows.Count - 1][i} = cell.CachedValue.ToString();
i++;
}
}
}
return dt;
}
}
And on a click-event, I am trying to pick my file using OpenFileDialog, see below:
OpenFileDialog of = new();
of.Filter = "Excel Files | *.xlsx;";
of.Title = "Import Excel file.";
if (of.ShowDialog()==true) {
dataGrid.ItemsSource = ImportExceltoDataTable("...", "...").DefaultView;
}
But I do not know how to notify the DataTable that I've chosen a file in my OpenFileDialog.
At the first line in the DataTable method, I get the following exception error:
System.ArgumentException: 'Empty extension is not supported'
Which makes sense... How can I tell it what file I've picked?
You may want to re-think your approach to reading the excel file. One possible issue is the if (firstRow) { … … statement, which is odd and makes a dangerous assumption. The code “assumes” that each column of data “has” a header cell. In other words, the number of columns added to the DataTable will be determined by the number of cells found (with some text in the cell) on the “FIRST” row. What if a column of data does NOT have a header cell?
Therefore, if there are any rows that have data to the right of the first row’s cells with headers, then the DataTable will not have the correct number of columns… and, when the code gets to these cells in the else portion below… the code will most likely crash when i goes beyond dt’s column count.
The code needs to guarantee that dt has the correct number of columns to avoid the problem described above. One way to help is to find the two cells that define the “top-left” cell where the data begins (as it may not necessarily always be the first cell in the worksheet) and “bottom-right” cell where the “last” cell with data is located.
Once we have these two cells (top-left and bottom-right)… then, we can determine how many columns are needed in the DataTable… and… we can almost guarantee that all the data in the worksheet will fit in the DataTable.
Below is one possible solution using the ideas described above. Note, the code below does not use a particular worksheet name and simply uses the first worksheet in the given workbook.
private void Button_Click(object sender, RoutedEventArgs e) {
OpenFileDialog of = new OpenFileDialog();
of.Filter = "Excel Files | *.xlsx;";
of.Title = "Import Excel file.";
if (of.ShowDialog() == true) {
dataGrid.ItemsSource = ImportExceltoDataTable(of.FileName).DefaultView;
}
}
public static DataTable ImportExceltoDataTable(string filePath) {
using (XLWorkbook wb = new XLWorkbook(filePath)) {
IXLWorksheet ws = wb.Worksheet(1);
int tl_Row = ws.FirstCellUsed().Address.RowNumber;
int tl_Col = ws.FirstCellUsed().Address.ColumnNumber;
int br_Row = ws.LastCellUsed().Address.RowNumber;
int br_Col = ws.LastCellUsed().Address.ColumnNumber;
DataTable dt = new DataTable();
// add dt columns using the first row of data
for (int i = tl_Col; i <= br_Col; i++) {
dt.Columns.Add(ws.Cell(tl_Row, i).CachedValue.ToString());
}
IXLRow currentRow;
// add data from the worksheet to dt - we already used the first row of data for the columns
for (int dtRow = 0; dtRow < br_Row - tl_Row; dtRow++) {
currentRow = ws.Row(tl_Row + dtRow + 1);
dt.Rows.Add();
for (int dtCol = 0; dtCol < br_Col - tl_Col + 1; dtCol++) {
dt.Rows[dtRow][dtCol] = currentRow.Cell(tl_Col + dtCol).CachedValue;
}
}
return dt;
}
}
I hope this makes sense and helps.
I am trying to fetch data from Excel sheet and fill data into an DataTable in c# by using EPPlus by this code:
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using(var _excel = new ExcelPackage(new FileInfo(fileAddress)){
var data = excel.Workbook.Worksheets[0].Cells.ToDataTable(options =>
{
options.FirstRowIsColumnNames = true;
options.EmptyRowStrategy = EmptyRowsStrategy.StopAtFirst;
});
}
but when I am running this code I am getting this error:
first row contains an empty cell at index 4
which 4 is the index of the first empty cell after data ends.
I try to set column mapping but no progress has been made. is there any way to determine the table dimension to cast to DataTable?
Add Nuget package of ClosedXML to the solution.
using ClosedXML.Excel;
call this function when you want in your code:
string strFileName = #"C:\VSTSInput\InputFileVSTS_File1.xlsx";
string strSheetName = "Sheet 1";
DataTable DT_InputData = GetDataFromExcel(strFileName, strSheetName);
Create the following as a method, to reuse it:
public static DataTable GetDataFromExcel(string path, dynamic worksheet)
{
DataTable dt = new DataTable();
//Open the Excel file using ClosedXML.
using (XLWorkbook workBook = new XLWorkbook(path))
{
//Read the first Sheet from Excel file.
IXLWorksheet workSheet = workBook.Worksheet(worksheet);
//Create a new DataTable.
//Loop through the Worksheet rows.
bool firstRow = true;
foreach (IXLRow row in workSheet.Rows())
{
//Use the first row to add columns to DataTable.
if (firstRow)
{
foreach (IXLCell cell in row.Cells())
{
if (!string.IsNullOrEmpty(cell.Value.ToString()))
{
dt.Columns.Add(cell.Value.ToString());
}
else
{
break;
}
}
firstRow = false;
}
else
{
int i = 0;
DataRow toInsert = dt.NewRow();
foreach (IXLCell cell in row.Cells(1, dt.Columns.Count))
{
try
{
toInsert[i] = cell.Value.ToString();
}
catch (Exception ex)
{
Console.WriteLine("Failed at: " + System.Reflection.MethodBase.GetCurrentMethod().Name);
Console.WriteLine(ex.Message);
}
i++;
}
dt.Rows.Add(toInsert);
}
}
return dt;
}
}
Your data from the excel will be added to the DataTable and you can access the columns by the following code:
foreach (DataRow dtRow in DT_InputData.Rows)
{
var dataColumn1 = dtRow[0].ToString(); //Data of 1st column in excel
var dataColumn2 = dtRow[1].ToString(); //Data of 2nd column in excel
var dataColumn3 = dtRow["Your Excel Column Name"].ToString();
}
I have sample code block and you can see below and this code working effectively. But this code not enough for me. So i need improve my code.
Firstly, i have datagridview and this datagridview creating my datablock but i could not write new row to under before row. When i add new data block to datagridvew then new data has must write under older data on excel file.
private void buttonExcel_Click(object sender, EventArgs e)
{
// creating Excel Application
Microsoft.Office.Interop.Excel._Application app = new Microsoft.Office.Interop.Excel.Application();
// creating new WorkBook within Excel application
Microsoft.Office.Interop.Excel._Workbook workbook = app.Workbooks.Add(Type.Missing);
// creating new Excelsheet in workbook
Microsoft.Office.Interop.Excel._Worksheet worksheet = null;
// see the excel sheet behind the program
app.Visible = true;
// get the reference of first sheet. By default its name is Sheet1.
// store its reference to worksheet
worksheet = workbook.Sheets["Sheet1"];
worksheet = workbook.ActiveSheet;
// changing the name of active sheet
worksheet.Name = "Exported from gridview";
// storing header part in Excel
for (int i = 1; i < dataGridView1.Columns.Count + 1; i++)
{
worksheet.Cells[1, i] = dataGridView1.Columns[i - 1].HeaderText;
}
// storing Each row and column value to excel sheet
for (int i = 0; i < dataGridView1.Rows.Count - 1; i++)
{
for (int j = 0; j < dataGridView1.Columns.Count; j++)
{
worksheet.Cells[i + 2, j + 1] = dataGridView1.Rows[i].Cells[j].Value.ToString();
}
}
// save the application
workbook.SaveAs("c:\\PROJE TEKLİF FİYAT.xlsx", Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
// Exit from the application
app.Quit();
}
I'd used the ClosedXML Library (via NuGet) for this as it makes saving a datatable to excel pretty simple.
First, import your existing spreadsheet as a datatable like so:
public DataTable GetData(string filename)
{
using (XLWorkbook workBook = new XLWorkbook(filename))
{
//Read the first Sheet from Excel file.
IXLWorksheet workSheet = workBook.Worksheet(1);
//Create a new DataTable.
DataTable dt = new DataTable();
//Loop through the Worksheet rows.
bool firstRow = true;
foreach (IXLRow row in workSheet.Rows())
{
//Use the first row to add columns to DataTable.
if (firstRow)
{
foreach (IXLCell cell in row.Cells())
{
dt.Columns.Add(cell.Value.ToString());
}
firstRow = false;
}
else
{
//Add rows to DataTable.
dt.Rows.Add();
int i = 0;
foreach (IXLCell cell in row.Cells(row.FirstCellUsed().Address.ColumnNumber, row.LastCellUsed().Address.ColumnNumber))
{
dt.Rows[dt.Rows.Count - 1][i] = cell.Value.ToString();
i++;
}
}
}
return dt;
}
}
Then convert datagridview to datatable, then merge the existing data w/ the datagridview data into one datatable, then save as XLSX.
using ClosedXML.Excel;
...
private void saveDGV(DataTable existing)
{
//Creating DataTable.
DataTable dt = new DataTable();
//Adding the Columns.
foreach (DataGridViewColumn column in dataGridView1.Columns)
{
dt.Columns.Add(column.HeaderText, column.ValueType);
}
//Adding the Rows.
foreach (DataGridViewRow row in dataGridView1.Rows)
{
dt.Rows.Add();
foreach (DataGridViewCell cell in row.Cells)
{
dt.Rows[dt.Rows.Count - 1][cell.ColumnIndex] = cell.Value.ToString();
}
}
existing.Merge(dt);
using (XLWorkbook wb = new XLWorkbook())
{
wb.Worksheets.Add(existing, "Exported from gridview");
//Adjust widths of Columns.
wb.Worksheet(1).Columns().AdjustToContents();
wb.SaveAs("c:\\PROJE TEKLİF FİYAT.xlsx")
}
}
You would use this like so:
using ClosedXML.Excel;
...
private void buttonExcel_Click(object sender, EventArgs e)
{
DataTable existing = GetData("c:\\PROJE TEKLİF FİYAT.xlsx");
saveDGV(existing);
}