Update Excel From C# Based on Column Name - c#

I am looking for a way to update an existing Excel spreadsheet with data from a SQL query via C# putting data in columns based on column header. For instance, if I have the following returned query/dataset
Width Height Length
2 2 2
2 3 4
3 4 5
And I have an Excel workbook like so:
Width Height Area Length Volume
=(A1*B1) =(C1*D1)
=(A2*B2) =(C2*D2)
=(A3*B3) =(C3*D3)
I would like to insert Width, Length and Height into the workbook without affecting Area or Volume, i.e.:
Width Height Area Length Volume
2 2 =(A1*B1) 2 =(C1*D1)
2 3 =(A2*B2) 4 =(C2*D2)
3 4 =(A3*B3) 5 =(C3*D3)
Is there a way to specify in code that the Width from the dataset should go in the Width column, etc.? I am currently using the EPPlus package to do Excel tasks.

A couple of approaches for this
1. You can hard-code the Excel column name's index
2. You can resolve it and put it in a dictionary
I'm going to go with option 2 so it's easier for you. However a couple of assumptions.
You know how to get the Worksheet property of your application through Interop.Excel
You are able to specify the row where you start entering the data, and row of where all the column names are
Here's the code
using Microsoft.Office.Interop.Excel;
public void SyncData(Worksheet ws, DataTable dt, int startRow){
//Get the columns and their corresponding indexes in excel
Dictionary<string, int> columnMap = ExcelColumnResolver(ws, dt, 1);
//The row number in excel youre starting to update from
int currRow = startRow;
//Iterate through the rows and the columns of each row
foreach(DataRow row in dt.Rows){
foreach(DataColumn column in dt.Columns){
//Only update columns we have mapped
if(columnMap.ContainsKey(column.ColumnName)){
ws.Cells[currRow, columnMap[column.ColumnName]] = row[column.ColumnName];
}
}
currRow++;
}
}
//columnsRow = Row in which the column names are located (non-zero indexed)
public Dictionary <string, int> ExcelColumnResolver(Worksheet ws, DataTable dt, int columnsRow) {
Dictionary<string, int> nameToExcelIdxMap = new Dictionary<string, int>();
//The row in Excel that your column names are located
int maxColumnCount = 10;
//Excel cells start at index 1
for (int i = 1; i < maxColumnCount; i++) {
string col = ws.Cells[columnsRow, i].ToString();
if (dt.Columns.Contains(col)){
nameToExcelIdxMap[col] = i;
}
}
return nameToExcelIdxMap;
}
Here's a tutorial on how you can access the Excel worksheet
Runtime is O(n^2), but for performance I would recommend:
Populating the data in an object array and using the Worksheet.Range
property to set a group of cells, instead of individually updating
the cells 1 by 1.
Parallel the writing of rows to the object
array, since there are no dependencies between the rows

Using EPPlus and assuming GetDataFromSql returns DataTable, you can use the following code:
var data = GetDataFromSql();
using (var excelPackage = new ExcelPackage(new FileInfo(#"C:\Proj\Sample\Book1.xlsx")))
{
var worksheet = excelPackage.Workbook.Worksheets.First();
// Get locations of column names inside excel:
var headersLocation = new Dictionary<string, Tuple<int, int>>();
foreach (DataColumn col in data.Columns)
{
var cell = worksheet.Cells.First(x => x.Text.Equals(col.ColumnName));
headersLocation.Add(col.ColumnName, new Tuple<int, int>(cell.Start.Row, cell.Start.Column));
}
for (var i = 0; i < data.Rows.Count; i++)
{
foreach (DataColumn col in data.Columns)
{
// update the value
worksheet.Cells[headersLocation[col.ColumnName].Item1 + i + 1,
headersLocation[col.ColumnName].Item2
].Value = data.Rows[i][col];
}
}
excelPackage.Save();
}

Related

C#, ClosedXML. Taking cell addresses from an Excel sheet, and taking data from a data table, and use this addresses and datas to fill a template file

Addresses Excel
I have an issue with my c# code with ClosedXML, which takes cell addresses from another excel file, takes data from another excel table, and writes data to an excel file which is a template but while taking the addresses, it's sorting them, if the data table has a certain value, it's should write the value to a certain address. For example, if the C5 cell in the data table has "SPHERE", it's gonna write to value to K1 cell, if the C5 cell in the data table has "BODY", it's gonna write to value to K5 cell, if the C5 cell in the data table is empty, it's not gonna use that parameter and directly write the value to G3, this example can be multiplied by different parameters.
But the problem is when it takes data from a cell first, it fills the defined cell in the if and saves the file, when it comes to a second file, it fills the first cell with the value before, then fills the second cell. It iterates like this until the parameter has different value.
public void Read(string path, string directory, bool savewithdatetime)
{
// Open workbook and get worksheet at index 1
XLWorkbook excelBook = new XLWorkbook(path);
IXLWorksheet excelWorkSheet = excelBook.Worksheet(1);
excelWorkSheet.Row(1).Delete();
// Get number of rows with data
int excelRowCount = excelWorkSheet.RowsUsed().Count();
// Open data source and get the worksheet at index 1
XLWorkbook dataSource = new XLWorkbook(Application.StartupPath + "/dataSource.xlsx");
IXLWorksheet dataSourceWS = dataSource.Worksheet(1);
List<string> ifList = new List<string>();
List<string> parameterList = new List<string>();
List<string> fromList = new List<string>();
List<string> toList = new List<string>();
for (int i = 1; i <= dataSourceWS.RowsUsed().Count(); i++)
{
ifList.Add(dataSourceWS.Cell("A" + i).Value.ToString());
parameterList.Add(dataSourceWS.Cell("D" + i).Value.ToString());
fromList.Add(dataSourceWS.Cell("B" + i).Value.ToString());
toList.Add(dataSourceWS.Cell("C" + i).Value.ToString());
}
string time = TimeStamp(savewithdatetime);
// Iterate over each row in the excelWorkSheet
for (int row = 1; row <= excelRowCount; row++)
{
// Reset templateWS to the original template
templateWS = template.Worksheet(1);
// Iterate through the data source rows
for (int i = 0; i < fromList.Count; i++)
{
// Check if the cell at the given address in excelWorkSheet is empty
if (string.IsNullOrEmpty(excelWorkSheet.Cell(ifList[i]).Value.ToString()))
{
// If empty, skip to the next iteration
continue;
}
// Check if the value of the cell at the given address in excelWorkSheet matches the value of the cell at the corresponding address in parameterList
else if (parameterList[i] == excelWorkSheet.Cell(ifList[i]).Value.ToString())
{
// If the values match, set the value of the cell at the corresponding address in toList to the value of the cell at the corresponding address in fromList
templateWS.Cell(toList[i]).Value = excelWorkSheet.Cell(fromList[i]).Value;
}
}
// Save template workbook as new file with incremented index in the specified directory
template.SaveAs(directory + "/Test results" + time + "/Test result-[" + (row) + "].xlsx");
if (row == excelRowCount)
{
break;
}
// Delete the first row of the excelWorkSheet so that the next iteration will use the next row of data
excelWorkSheet.Row(1).Delete();
}
// Clear the lists and collect garbage to free up memory
ifList.Clear();
fromList.Clear();
parameterList.Clear();
toList.Clear();
GC.Collect();
}

How to add a column and values in to existing Data column from a string array

I have to add a data column in to the existing data table.
I need to add one more column and data to that table from a .txt file in to 0th column and from second column I need to keep the DB data.
I am not using any grid view and stream reader to my app. I need to use File.ReadAllLines();
My .txt file is like:
ClaimID
val1
val3
val5
I tried something and the data table is not coming properly. Data table coming like showing in the picture below (data is starting from last row of first column and skipped first rows):
What I have tried is:
if (tableName == "TestData")
{
var pathC = #"H:\claimdetails\claims\Claims.txt";
string[] result = File.ReadAllLines(pathC);
DataColumn Col = table.Columns.Add("Claim_ID", typeof(String));
Col.SetOrdinal(0); // set column to first position
DataRow row;
for (int i = 0; i < result.Length; i++)
{
row = table.NewRow();
row["Claim_ID"] = result[i];
table.Rows.Add(row);
}
}
adapter.Fill(table);
How can I get a proper data table?
Instead of adding rows to the data table, you can try,
table.Rows[i].[0] = result[i]

retrieving Column cells value from datagridview C#

I can't figure out how to solve this. I want to calculate in datagridview
Column 3 like this
using C#
Check this
int sum = 0;
for(int i=0;i<dataGridView1.Rows.Count;i++)
{
sum += Convert.ToInt32(dataGridView1.Rows[i].Cells[0].Value);
}
MessageBox.Show(sum.ToString());
To calculate values from you datagridview, you can do several approaches
For example, you can retrieve by rows then reference the column of the cell by index or column name.
For example, adding values in "Column 3" would be:
var rows = dataGridView1.rows;
double sum = 0;
foreach (DataGridViewRow row in rows)
{
sum += (double)row.Cells["Column 3"].Value;
}
EDIT:
If you're using DataTable (which I presume from your comments) to get individual cell data, you can either reference the row first then adding an index based on the column, or you can create a temp row, then get the value using the column. The records or values within a DataTable are basically just collection of rows.
Example:
int col3 = workTable.Columns.IndexOf("Column 3") -1;
double amount = Convert.ToDouble(workTable.Rows[1][col3]); // 1st value in column 3
OR (extended for clarity)
DataRow row1 = workTable.Rows[1];
double amount = Convert.ToDouble(row1[col3]);
If you need to transpose values into a DataGridView, you can just set the DataSource property of a DataGridView instance i.e.
dataGridView1.DataSource = newFilledTable;

How do I exclude the default named column having no data in my excelsheet which I am fetching in a dataset

I am having an excel sheet with multiple rows and columns which I am fetching it in a dataset in my .NET application(using C#). When I see the returned dataset through a simple SELECT query, the excel sheet is having the required columns that I want and also there are some blank columns in between them which by default is given the name F1, F2, F3 and so on. What I want to do is remove these columns and only get the required columns that I want.
Thanks in advance.
Try it with DataTable and DataView:
DataTable oldTable = dataSet.Tables[0];
DataTable newTable = oldTable.DefaultView.ToTable(false, "Only columns you want", "Only columns you want");
With all columns names dynamic you might try this:
for (int col = dataTable.Columns.Count - 1; col >= 0; col--)
{
bool removeColumn = true;
foreach (DataRow row in dataTable.Rows)
{
if (!row.IsNull(col))
{
removeColumn = false;
break;
}
}
if (removeColumn)
dataTable.Columns.RemoveAt(col);
}
Or shorter LinQ version:
for (var col = dataTable.Columns.Count - 1; col >= 0; col--)
{
bool removeColumn = dataTable.Rows.Cast<DataRow>().All(row => row.IsNull(col));
if (removeColumn)
dataTable.Columns.RemoveAt(col);
}
#Yeronimo
your answer for dynamic columns is working fine... thank you very much sir...
and some related articles which might help
Check if rows in an excel sheet are empty with asp.net
Remove Column From GridView While Converting To Excel
http://www.daniweb.com/software-development/csharp/threads/297806/how-can-i-remove-empty-columns-in-spreadsheet

Select range in aspose

Do you know an equivalent to VBA code:
Range(Selection, Selection.End(xlToRight)).Select
In Aspose.Cells. It seems that its only possible to select the last cell in the entire row:
public Aspose.Cells.Cell EndCellInRow ( Int32 rowIndex )
Or the last cell on the right within a range:
public Aspose.Cells.Cell EndCellInRow ( Int32 startRow, Int32 endRow, Int32 startColumn, Int32 endColumn )
but then you must know more or less how big your table is going to be.
I found this from 2009: http://www.aspose.com/community/forums/permalink/196519/196405/showthread.aspx but that will not resolve my problem as I may have many tables in a sheet both horizontally and vertiacally. And I can't predict where they are going to be.
Edit1:
Sorry if this is dumb question, but ctrl+shift+arrow is such a common operation that I can't believe it would be not implemented so I'm making sure I really have to re-invent the wheel.
Aspose.Cells provides the list of tables in a worksheet using property named 'Worksheet.ListObjects'. 'ListObjects' is a colloection of 'ListObject' type which represents a Table in an excel sheet. That means if one has more than one Tables in a worksheet, the ListObjects collection will give access to every table in the worksheet very conveniently. Each 'ListObject' in turn contains a property named 'DataRange' which specifies all the cells inside a Table. For the sake of convenience DataRange can be used for following operations on a Table:
To apply styles/formatting on the cells in Table
To get the data values
Merge or move the cells in Range
Export contents
To get enumerator to traverse through Table cells
To make selection of cells from DataRange, you can traverse using DataRange to get all the cells in a Row (This could also be done for a column)
Applying any operation on Table cells like after selecting cells using Ctrl+Shift+Arrow, could be performed using a workbook object as follows:
Workbook workbook = new Workbook(new FileStream("book1.xls", FileMode.Open));
if (workbook.Worksheets[0].ListObjects.Count > 0)
{
foreach (ListObject table in workbook.Worksheets[0].ListObjects)
{
Style st = new Style();
st.BackgroundColor = System.Drawing.Color.Aqua;
st.ForegroundColor = System.Drawing.Color.Black;
st.Font.Name = "Agency FB";
st.Font.Size = 16;
st.Font.Color = System.Drawing.Color.DarkRed;
StyleFlag stFlag = new StyleFlag();
stFlag.All = true;
table.DataRange.ApplyStyle(st, stFlag);
}
}
workbook.Save("output.xls");
There is also some worthy information available in Aspose docs about Table styles and applying formatting on a ListObject. For getting last Table cell in a certain row or column, I am sure this will help:
int iFirstRowIndex = table.DataRange.FirstRow;
int iFirstColumnIndex = table.DataRange.FirstColumn;
int iLastRowIndex = table.DataRange.RowCount + iFirstRowIndex;
int iLastColumnIndex = table.DataRange.ColumnCount + iFirstColumnIndex;
for (int rowIndex = 0; rowIndex < table.DataRange.RowCount; rowIndex++)
{
//Get last cell in every row of table
Cell cell = worksheet.Cells.EndCellInColumn(rowIndex + iFirstRowIndex, rowIndex + iFirstRowIndex, (short)iFirstColumnIndex, (short)(iLastColumnIndex - 1));
//display cell value
System.Console.WriteLine(cell.Value);
}

Categories

Resources