Getting the last filled excel row using C# per specific column - c#

Getting the last Excel row in a given worksheet has lots of nice answers here.
However, sometimes all we need is the last row of a given column and not of the whole spreadsheet.
I thought of a solution, which seems a bit slow:
find the last row in Excel for all columns;
start looping from there to 1, in the specific column, trying to find the first cell which is not empty. This is the result;
for empty first row and only first row in a given column, always return 1;
This is the implementation:
namespace ExcelTest
{
using System;
using Excel = Microsoft.Office.Interop.Excel;
public class Startup
{
const string filePath = #"C:\Users\gropc\Desktop\Sample.xlsx";
static void Main()
{
Excel.Application excel = new Excel.Application { Visible = true, EnableAnimations = false };
Excel.Workbook wkb = Open(excel, filePath);
foreach (Excel.Worksheet wks in wkb.Worksheets)
{
int lastRowA = LastRowPerColumn(1, wks);
int lastRowB = LastRowPerColumn(2, wks);
int lastRowC = LastRowPerColumn(3, wks);
Console.WriteLine($"{lastRowA} - {lastRowB} - {lastRowC}");
}
wkb.Close(true);
excel.Quit();
}
static int LastRowPerColumn(int column, Excel.Worksheet wks)
{
int lastRow = LastRowTotal(wks);
while (((wks.Cells[lastRow, column]).Text == "") && (lastRow != 1))
{
lastRow--;
}
return lastRow;
}
static int LastRowTotal(Excel.Worksheet wks)
{
Excel.Range lastCell = wks.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
return lastCell.Row;
}
static Excel.Workbook Open(Excel.Application excelInstance,
string fileName, bool readOnly = false,
bool editable = true, bool updateLinks = true)
{
return excelInstance.Workbooks.Open(fileName, updateLinks, readOnly);
}
}
}
Dependencies:
using Excel = Microsoft.Office.Interop.Excel;
const string filePath = #"C:\Users\gropc\Desktop\Sample.xlsx";
Question:
Any ideas to avoid the looping? In vba the solution is quite charming in 1 line:
lastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row
Anything similar for the C# Excel Interop?

Related

Need assistance inserting certain data from datatable to Excel dynamically C#

I created a datatable from an excel sheet. I ran some linq queries and populated the needed columns in a new worksheet. I need help filling in the rows based on the date column. It needs to be dynamic because dates change and IDs could change.
I have an excel sheet that has data like this
L ID | Date | Other | Columns |
D123 1/24/2018
D456 1/25/2018
D678 1/26/2018
D910 1/25/2018
I am trying to create output that looks like this.
L ID 1/24/2018 1/25/2018 1/26/2018
D123 1
D456 1
D678 1
D910 1
I did some code that selected the data needed and then populated the date columns and the ID column in an excel worksheet.
public List<DateTime?> GetTurnOverDates(System.Data.DataTable dt3)
{
List<DateTime?> TurnOverDateList = dt3.AsEnumerable().Select(r => r.Field<DateTime?>("Turnover")).Distinct().OrderBy(x => x).Where(x => x >= DateTime.Today.AddDays(-1)).ToList();
return TurnOverDateList;
public List<String> GetLIDList(System.Data.DataTable dt3, List<DateTime?> list)
{
List<String> LIDList = dt3.AsEnumerable().Where(r => list.Contains(r.Field<DateTime?>("Turnover"))).Select(r => r.Field<String>("LID")).ToList();
return LidList;
}
public void CreateTurnoverWS(String Path, List<DateTime?> DateList, List<String> LidList)
{
int rw = 0;
int c1 = 0;
Excel.Application xlsApp;
Excel.Workbook xlsWorkbook;
Excel.Range range;
xlsApp = new Excel.Application();
xlsWorkbook = xlsApp.Workbooks.Open(Path);
Excel.Sheets xlsworksheets = xlsWorkbook.Worksheets;
var xlsNewSheet = (Excel.Worksheet)xlsworksheets.Add(xlsworksheets[1], Type.Missing, Type.Missing, Type.Missing);
xlsNewSheet.Name = "Turnover";
xlsNewSheet.Cells[1, 3] = "L ID";
xlsNewSheet = (Excel.Worksheet)xlsWorkbook.Worksheets.get_Item(1);
xlsNewSheet.Select();
range = xlsNewSheet.UsedRange;
rw = range.Rows.Count;
c1 = range.Columns.Count;
for (int cCnt = 1; cCnt < DateList.Count(); cCnt++)
{
xlsNewSheet.Cells[1, c1++] = DateList[cCnt];
}
for (int ccnt2 = 0; ccnt2 < LidList.Count(); ccnt2++)
{
xlsNewSheet.Cells[rw++, 3] = LidList[ccnt2];
}
xlsWorkbook.Save();
xlsWorkbook.Close();
}
}
So I basically have this so far in the excel sheet.
L ID 1/24/2018 1/25/2018 1/26/2018
D123
D456
D678
D910
I was researching pivot tables but I am not sure if it will work. I have data going from excel to a datatable, selecting certain data then putting it into a new worksheet. Please advise. Thanks.
For „pivot“-ing have a look at Is it possible to Pivot data using LINQ?
PS: For accessing excel files from code I usually use epplus

How to create a Excel Row and insert it in Excel Range?

I have an excel worksheet as below (just an example)
I have created a Microsoft.Office.Interop.Excel.Range object referring the range from Item1 to Category5 (selected cells in the above image).
Now I want to create a new Row (Market1, Market2, Market3, Market4, Market5) and add it below the range ie., below Category row.
I am using the Microsoft.Office.Interop.Excelclasses for the first time.
Can someone help me in figuring out how to create and add a new row to an existing range object.
Here is the code which I have written -
public class Class1
{
static void Main(string[] args)
{
Application appExcel = new Application();
WorkBook workBook = appExcel.Workbooks.Open(#"C:\Data.xlsx", true, false);
workSheet = (Worksheet)workBook.Sheets["Export"];
Range usedRange = workSheet.UsedRange;
Range itemCatRange = GetSection(usedRange, "Item1","Group1"); //Gets the selected range as shown in pic
//Here I want to create a new row of cells and add the newly created row at the end of the above range "itemCatRange"
}
private static Range GetSection(Range usedRange, string startHeader, string endHeader)
{
string str = string.Empty;
string end = String.Empty;
Range algAlmRange;
foreach (Range row in usedRange.Rows)
{
object firstColumnValue = row.Columns.Value2[1, 1];
if (firstColumnValue != null)
{
if (firstColumnValue.ToString() == startHeader)
{
str = row.Address;
}
else if (firstColumnValue.ToString() == endHeader)
{
end = row.Address;
}
}
}
algAlmRange = workSheet.Range[str, end];
return algAlmRange;
}
}
Something like
Range itemCatRange = GetSection(usedRange, "Item1","Group1");
Range lastRow = itemCatRange[itemCatRange.Rows, 1].EntireRow;
lastRow.Insert(XlDirection.xlDown, XlInsertFormatOrigin.xlFormatFromLeftOrAbove);
You may have to go down another row, or use xlUp. I haven't actually tried this.

Open XML not saving when adding data C#

I did a complete copy from the following link: https://msdn.microsoft.com/en-us/library/dd452407(v=office.12).aspx
The copy from template works fine, the FixChartData() method works fine. However, the output File does not contain any data. I do see that the contentRow contains the data via the debugger, but the excel sheet does not have the data in it when I open the file.
Very frustrating. Any help would be appreciated.
public void Create()
{
string appPath = System.IO.Path.GetDirectoryName(System.IO.Path.GetDirectoryName(System.IO.Directory.GetCurrentDirectory()));
string templateFile = appPath + #"\Templates\ChartExample.xlsx";
string saveFile = appPath + #"\Documents\Generated.xlsx";
File.Copy(templateFile, saveFile, true);
//open copied template.
using(SpreadsheetDocument myWorkbook = SpreadsheetDocument.Open(saveFile, true))
{
//this is the workbook contains all the worksheets
WorkbookPart workbookPart = myWorkbook.WorkbookPart;
//we know that the first worksheet contains the data for the graph
WorksheetPart worksheetPart = workbookPart.WorksheetParts.First(); //getting the first worksheet
//the shhet data contains the information we are looking to alter
SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();
int index = 2;//Row the data for the graph starts on
//var qry = from t in db.SEL_SE_DEATHS()
FudgeData fudge = new FudgeData();
var qry = fudge.Fudged();
foreach(var item in qry)
{
int Year = item.EventYear;
int PSQ = item.PSQReviewable;
int death = item.Deaths;
Row contentRow = CreateContentRow(index, Year, PSQ, death);
index++;
//contentRow.RowIndex = (UInt32)index;
sheetData.AppendChild(contentRow);
}
//(<x:c r="A2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>2014</x:v></x:c><x:c r="B2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>21</x:v></x:c><x:c r="C2" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><x:v>4</x:v></x:c>)
FixChartData(workbookPart, index);
worksheetPart.Worksheet.Save();
myWorkbook.Close();
myWorkbook.Dispose();
}
}
string[] headerColumns = new string[] { "A", "B", "C" }; //the columns being accessed
public Row CreateContentRow(int index, int year, int pSQ, int death)
{
Row r = new Row();
r.RowIndex = (UInt32)index;
//skipping the text add function
//we are createing a cell for each column (headerColumns),
//for each cell we are adding a value.
//we then append the value to the cell and append the cell to the row - wich is returned.
for(int i =0; i <headerColumns.Length; i++)
{
Cell c = new Cell();
c.CellReference = headerColumns[i] + index;
CellValue v = new CellValue();
if(i == 0)
{
v.Text = year.ToString();
}else if(i == 1)
{
v.Text = pSQ.ToString();
}else if(i == 2)
{
v.Text = death.ToString();
}
c.AppendChild(v);
r.AppendChild(c);
}
return r;
}
//Method for when the datatype is text based
public Cell CreateTextCell(string header, string text, int index)
{
//Create a new inline string cell.
Cell c = new Cell();
c.DataType = CellValues.InlineString;
c.CellReference = header + index;
//Add text to the text cell.
InlineString inlineString = new InlineString();
Text t = new Text();
t.Text = text;
inlineString.AppendChild(t);
c.AppendChild(inlineString);
return c;
}
//fix the chart Data Regions
public void FixChartData(WorkbookPart workbookPart, int totalCount)
{
var wsparts = workbookPart.WorksheetParts.ToArray();
foreach(WorksheetPart wsp in wsparts)
{
if(wsp.DrawingsPart != null)
{
ChartPart chartPart = wsp.DrawingsPart.ChartParts.First();
////change the ranges to accomodate the newly inserted data.
foreach (DocumentFormat.OpenXml.Drawing.Charts.Formula formula in chartPart.ChartSpace.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Formula>())
{
if (formula.Text.Contains("$2"))
{
string s = formula.Text.Split('$')[1];
formula.Text += ":$" + s + "$" + totalCount;
}
}
chartPart.ChartSpace.Save();
}
}
//ChartPart chartPart = workbookPart.ChartsheetParts.First().DrawingsPart.ChartParts.First();
////change the ranges to accomodate the newly inserted data.
//foreach(DocumentFormat.OpenXml.Drawing.Charts.Formula formula in chartPart.ChartSpace.Descendants<DocumentFormat.OpenXml.Drawing.Charts.Formula>())
//{
// if (formula.Text.Contains("$2"))
// {
// string s = formula.Text.Split('$')[1];
// formula.Text += ":$" + s + "$" + totalCount;
// }
//}
//chartPart.ChartSpace.Save();
}
David,
I got your code to work fine. Here is a link to my Console Application.. I uploaded it to Github with some minor changes. I made 2 changes:
1) I was not able to download the samples from the link you provided. So I created a blank empty spreadsheet with Excel2016 and saved it in that directory.
2) The Fudge data was missing, so I generated some sample data via self mocked object.
The spreadsheet copies fine from the template and your code populates it with the fudge data. Here is what the final result looks like:
After downloading, you will need to make a Template and Document subdirectory. Then place my ChartExample.xslx file in the Template directory and run.

How to read excel list elements (data validation) using C# Excel Interop?

I have an Excel file with a cell that has set "Data Validation" to "List". Thanks to that, this cell is a drop down list. How can I read the elements of this list using C# Excel Interop? I can easily read the currently selected value:
Range range = xlWorkSheet.UsedRange; // Worksheet
string cellValue = (range.Cells[x, y] as Excel.Range).Value2.ToString();
but I cannot read the content of the dropdown list that this cell contains.
As it was stated here How do I read the values of Excel dropdowns or checkboxes from c# or vb.net? there is no easy way to do that, but it is possible to create custom function and do it manually. Below is my function that that reads drop down values into a string list. This function is based on an a question mentioned before, but I added support for formulas on other sheets.
List<string> ReadDropDownValues(Excel.Workbook xlWorkBook, Excel.Range dropDownCell)
{
List<string> result = new List<string>();
string formulaRange = dropDownCell.Validation.Formula1;
string[] formulaRangeWorkSheetAndCells = formulaRange.Substring(1, formulaRange.Length - 1).Split('!');
string[] splitFormulaRange = formulaRangeWorkSheetAndCells[1].Split(':');
Excel.Worksheet xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(formulaRangeWorkSheetAndCells[0]);
Excel.Range valRange = (Excel.Range)xlWorkSheet.get_Range(splitFormulaRange[0], splitFormulaRange[1]);
for (int nRows = 1; nRows <= valRange.Rows.Count; nRows++)
{
for (int nCols = 1; nCols <= valRange.Columns.Count; nCols++)
{
Excel.Range aCell = (Excel.Range)valRange.Cells[nRows, nCols];
if (aCell.Value2 != null)
{
result.Add(aCell.Value2.ToString());
}
}
}
return result;
}

EPPlus, Find and set the value for a Named Range

I've been pulling my hair out trying to set the value of a named range (in this case, a single named cell) using the ExcelPackage (3.0.1) library, it should be a simple as this:
ExcelNamedRange er = xlPackage.Workbook.Names["Customer"];
er.Value = "Foo Bar";
I'm obviously doing it wrong - has anyone got an example I can follow
Thanks
I looked for ExcelPackage documentation to see what type Names[] collection returns and found that documentatios will come soon, or at least that is what they said back in 2007.
I suggest you use EPPlus wich is a excel library (xlsx only) that have worked great to me.
official link
Now, to set a value for each cell in a named range:
ExcelWorksheet sheet = _openXmlPackage.Workbook.Worksheets["SheetName"];
using (ExcelNamedRange namedRange = sheet.Names["RangeName"])
{
for (int rowIndex = Start.Row; rowIndex <= namedRange.End.Row; rowIndex++)
{
for (int columnIndex = namedRange.Start.Column; columnIndex <= namedRange.End.Column; columnIndex++)
{
sheet.Cells[rowIndex, columnIndex].Value = "no more hair pulling";
}
}
}
I had to put in a work around using a cell value instead.
using (ExcelPackage xlPackage = new ExcelPackage(newFile))
{
foreach (ExcelWorksheet worksheet in xlPackage.Workbook.Worksheets)
{
var dimension = worksheet.Dimension;
if (dimension == null) { continue; }
var cells = from row in Enumerable.Range(dimension.Start.Row, dimension.End.Row)
from column in Enumerable.Range(dimension.Start.Column, dimension.End.Column)
//where worksheet.Cells[row, column].Value.ToString() != String.Empty
select worksheet.Cells[row, column];
try
{
foreach (var excelCell in cells)
{
try
{
if (excelCell.Value.ToString().Equals("[Customer]")) { excelCell.Value = "Customer Name"; }
}
catch (Exception) { }
}
}
catch (Exception a) { Console.WriteLine(a.Message); }
}

Categories