How to skip =<2 blank row using Datatable C#? - c#

Allow users to leave completely empty one or two empty rows. If any of the cells in rows have been filled out, then call out for the users to fill out the rest of the cells and tell them which row/line that cell lives in to fill.
Ideal logic to implement is: If empty row is found, skip it and go the the next row and find if any cell is left to fill,if found empty, skip go the next one.
I have two classes. The class below makes sure if the row is completely empty.
public bool isRowEmpty(DataTable dt, int index)
{
// check if index exists, if not returns false
// it will means that the row is "not empty"
if (index >= dt.Rows.Count || index < 0)
return false;
// Get row
DataRow dr = dt.Rows[index];
// Amount of empty columns
int emptyQt = 0;
// Run thourgh columns to check if any of them are empty
for (int i = 0; i < dr.ItemArray.Length; i++)
{
// If empty, add +1 to the amount of empty columns
if (string.IsNullOrWhiteSpace(dr.ItemArray[i].ToString()))
emptyQt++;
}
// if the amount of empty columns is equals to the amount of
//columns, it means that the whole row is empty
return emptyQt == dr.Table.Columns.Count;
}
Using the class above, I determine which row is empty within the next class, if found empty I will skip and go the next row, if found not empty find any cells that are not filled.
But the code below is not skipping the complete blank rows. Any insights?
public DataValidationModel Validate(DataTable data, IList<FieldModel> fields)
{
var fieldsSorted = fields.Where(f => f.IsInTaxonomy == true).OrderBy(f => f.TaxonomyPosition).ToList();
var model = new DataValidationModel()
{
Errors = new List<RowErrorModel>()
};
int rowCounter = 7;
for (int i =0; i < data.Rows.Count - 1; i++) //Rows.Count - 1,
{
if (!isRowEmpty(data, rowCounter-1) && isRowEmpty(data, rowCounter) && !isRowEmpty(data, rowCounter + 1))
i+=1;
if (data.Rows[rowCounter][0] == DBNull.Value || String.IsNullOrWhiteSpace(data.Rows[i][0].ToString()))
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "The name cannot be blank."
});
}
if (data.Rows[rowCounter]["Site"] == DBNull.Value || String.IsNullOrWhiteSpace(data.Rows[i]["Site"].ToString()))
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "Site is required."
});
}
if (data.Rows[rowCounter]["start date"] == DBNull.Value)
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "start date is required."
});
}
if (data.Rows[rowCounter]["end date"] == DBNull.Value)
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "end date is required."
});
}
if (data.Rows[rowCounter]["Placement Type"] == DBNull.Value)
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "Placement Type is required."
});
}
if (data.Rows[rowCounter]["Channel"] == DBNull.Value)
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "Channel is required."
});
}
if (data.Rows[rowCounter]["Environment"] == DBNull.Value)
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "Environment is required."
});
}
if (data.Rows[rowCounter]["rate type"] == DBNull.Value)
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "rate is required when a rate type is not blank."
});
}
if (data.Rows[rowCounter]["units"] == DBNull.Value)
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "units is required when a rate type is not blank."
});
}
if (data.Rows[rowCounter]["cost"] == DBNull.Value)
{
model.Errors.Add(new RowErrorModel()
{
Row = rowCounter,
Error = "cost is required when a rate type is not blank."
});
}
model.Errors = model.Errors.OrderBy(f => f.Row).ToList();
return model;
}

I show you an example which is not related to your additional logic.
public bool isRowEmpty(DataTable dt, int index)
{
DataRow row = dt.Rows[index];
return dt.Columns.Cast<DataColumn>()
.All(c => row.IsNull(c) || string.IsNullOrWhiteSpace(row[c].ToString()));
}
In the foreach or for-loop you just need to use continue:
for (int i = 0; i < data.Rows.Count; i++)
{
if (isRowEmpty(data, i))
continue;
// ...
}

Why can't you use continue once you found that the row is empty per your comment like
for (int i =0; i < data.Rows.Count - 1; i++) //Rows.Count - 1,
{
if (!isRowEmpty(data, rowCounter-1) && isRowEmpty(data, rowCounter) && !isRowEmpty(data, rowCounter + 1))
continue; // this one here, which will jump to next iteration

Related

Trying to read excel but getting System.ArgumentOutOfRangeException if user deletes row

Using openxml to read an excel file and write data into a database.
Data from the spreadsheet is stored in a data table and then mapped into a object array. The problem is: if a user deletes a row in the excel file I get the exception 'Specified argument was out of the range of valid values.
Parameter name: index' when reading the file.
To counter this, I have tried checking if the cell value is null, if so then take it as an empty string. However, the error occurs when I reach the 9th column in the last "deleted" row (8 columns out of 10 are taken as an empty string).
IEnumerable<Row> rows = sheetData.Descendants<Row>();
foreach (Row row in rows)
{
DataRow tempRow = dt.NewRow();
for (int i = 0; i < tempRow.ItemArray.Count(); i++)
{
//I get the exception here
Cell c = row.Descendants<Cell>().ElementAt(i);
if (c.CellValue != null)
{
tempRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i));
}
else if (c.CellValue == null )
{
tempRow[i] = "";
}
}
dt.Rows.Add(tempRow);
}
Solved by checking if we are within the range of the available columns in the row. Also checking if the first cell in the row is empty, discarding it if it is.
foreach (Row row in rows)
{
bool isEmpty = false;
DataRow tempRow = dt.NewRow();
for (int i = 0; i < tempRow.ItemArray.Count(); i++)
{
if (i < row.Descendants<Cell>().Count())
{
Cell c = row.Descendants<Cell>().ElementAt(i);
if (c.CellValue != null)
{
tempRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i));
}
else
{
if (i == 0)
{
isEmpty = true;
break;
}
else tempRow[i] = "";
}
}
else
{
tempRow[i] = "";
}
}
if (isEmpty) continue;
dt.Rows.Add(tempRow);
}

How to read data fast from an excel and convert it to list from file stream

I am using EPPlus.
The excel I am uploading has column headers in row number 2 . And from row 4 onward it has the data which may vary up to 2k records.
The way I am doing it , it takes a lot of time for reading 2k records and putting to a list .
using (var excel = new ExcelPackage(hpf.InputStream))
{
var ws = excel.Workbook.Worksheets["Sheet1"];
//Read the file into memory
for (int rw = 4; rw <= ws.Dimension.End.Row; rw++)
{
if (!ws.Cells[rw, 1, rw, 24].All(c => c.Value == null))
{
int headerRow = 2;
GroupMembershipUploadInput gm = new GroupMembershipUploadInput();
for (int col = ws.Dimension.Start.Column; col <= ws.Dimension.End.Column; col++)
{
var s = ws.Cells[rw, col].Value;
if (ws.Cells[headerRow, col].Value.ToString().Equals("Existing Constituent Master Id"))
{
gm.cnst_mstr_id = (ws.Cells[rw, col].Value ?? (Object)"").ToString();
}
else if (ws.Cells[headerRow, col].Value.ToString().Equals("Prefix of the constituent(Mr, Mrs etc)"))
{
gm.cnst_prefix_nm = (ws.Cells[rw, col].Value ?? (Object)"").ToString();
}
}
lgl.GroupMembershipUploadInputList.Add(gm);
}
}
GroupMembershipUploadInputList is the list of objects of type GroupMembershipUploadInput that I am adding the excel values to after reading from the cell wise.
Can it be done faster ? What am I missing here ?
Please help to improve the performance.
You are making a lot iterations there, for each row, you visit each column twice. I assume that you only need those two values per row and if so the following code would reduce time drastically:
using (var excel = new ExcelPackage(hpf.InputStream))
{
var ws = excel.Workbook.Worksheets["Sheet1"];
int headerRow = 2;
// hold the colum index based on the value in the header
int col_cnst_mstr_id = 2;
int col_cnst_prefix_nm = 4;
// loop once over the columns to fetch the column index
for (int col = ws.Dimension.Start.Column; col <= ws.Dimension.End.Column; col++)
{
if ("Existing Constituent Master Id".Equals(ws.Cells[headerRow, col].Value))
{
col_cnst_mstr_id = col;
}
if ("Prefix of the constituent(Mr, Mrs etc)".Equals(ws.Cells[headerRow, col].Value))
{
col_cnst_prefix_nm = col;
}
}
//Read the file into memory
// loop over all rows
for (int rw = 4; rw <= ws.Dimension.End.Row; rw++)
{
// check if both values are not null
if (ws.Cells[rw, col_cnst_mstr_id].Value != null &&
ws.Cells[rw, col_cnst_prefix_nm].Value != null)
{
// the correct cell will be selcted based on the column index
var gm = new GroupMembershipUploadInput
{
cnst_mstr_id = (string) ws.Cells[rw, col_cnst_mstr_id].Value ?? String.Empty,
cnst_prefix_nm = (string) ws.Cells[rw, col_cnst_prefix_nm].Value ?? String.Empty
};
lgl.GroupMembershipUploadInputList.Add(gm);
}
}
}
I removed the inner column loop and moved it to the start of the method. There it is used to just get the columnindex for each field you're interested in. The expensive null check can now also be reduced. To fetch the value, all that is now needed is a simple index lookup in the row.

Improve performance in converting large execl file to list of objects

I know this question have been asked multiple times . But I could not find much help from anyone of those.
I don't want to convert the excel into data table but I want it to be converted to a list of objects and sent to server side for processing.
If it has more than 2K rows it should throw an error. Currently what I am doing is something like :
using (var excel = new ExcelPackage(hpf.InputStream))
{
var ws = excel.Workbook.Worksheets["Sheet1"];
for (int rw = 4; rw <= ws.Dimension.End.Row; rw++)
{
if (ws.Cells[rw, 1].Value != null)
{
int headerRow = 2;
GroupMembershipUploadInput gm = new GroupMembershipUploadInput();
for (int col = ws.Dimension.Start.Column; col <= ws.Dimension.End.Column; col++)
{
var s = ws.Cells[rw, col].Value;
if (ws.Cells[headerRow, col].Value.ToString().Equals("Existing Constituent Master Id"))
{
gm.cnst_mstr_id = (ws.Cells[rw, col].Value ?? (Object)"").ToString();
}
else if (ws.Cells[headerRow, col].Value.ToString().Equals("Prefix of the constituent(Mr, Mrs etc)"))
{
gm.cnst_prefix_nm = (ws.Cells[rw, col].Value ?? (Object)"").ToString();
}
else if (ws.Cells[headerRow, col].Value.ToString().Equals("First Name of the constituent(Mike)"))
{
gm.cnst_first_nm = (ws.Cells[rw, col].Value ?? (Object)"").ToString();
}
.....................
.....................
}
}
iUploadedCnt = iUploadedCnt + 1; //Increase the count by 1
}
if (lgl.GroupMembershipUploadInputList.Count < 2003) //Check for the uploaded list count
{
//throw the error
}
But this approach seems slow.
Conversion of the excel to list seems slow to me. For example , when I upload more than 2k records , the list gets converted first to list and then the count is checked if more than 2003 . This process is definitely slower.
How can it be achieved in a faster /better way ?
You do a lot of repeated string processing which is unnecessary. For each row you check the column headers again if they fit some predefined value. (for instance if (ws.Cells[headerRow, col].Value.ToString().Equals("Existing Constituent Master Id")).
You could do this once before you start parsing all rows and create for instance a Dictionary<int, SomeEnum> which maps the column number to a specific enum value. When parsing the rows you then can make a quick lookup in the dictionary, which column maps to which property.
Furthermore, you define a var s = ws.Cells[rw, col].Value; but never use it. Instead, you read this cell value again, when you assign it to a property of your object. You could just make the necessary conversions and checks here, and then use only s;
// define this enum somewhere
enum ColumPropEnum {
cnst_mstr_id, cnst_prefix_nm, ...
}
//define this prop somewhere
Dictionary<int, ColumnPropEnum> colprops = new Dictionary<int, ColumnPropEnum>();
//do this once before processing all rows
for (int col = ws.Dimension.Start.Column; col <= ws.Dimension.End.Column; col++) {
if (ws.Cells[headerRow, col].Value.ToString().Equals("Existing Constituent Master Id"))
colprops.Add(col, ColumnPropEnum.cnst_mstr_id);
else if (ws.Cells[headerRow, col].Value.ToString().Equals(" ..."))
colprops.Add(col, ColumnPropEnum.cnst_prefix_nm);
...
}
//now use this dictionary in each row
for (int rw = 4; rw <= ws.Dimension.End.Row; rw++)
{
....
for (int col = ws.Dimension.Start.Column; col <= ws.Dimension.End.Column; col++) {
//the single ? checks, whether the Value is null, if yes it returns null, otherwise it returns ToString(). Then the double ?? checks whether the result if the operation is null, if yes, it assigns "" to s, otherwise the result of ToString();
var s = ws.Cells[rw, col].Value?.ToString() ?? "";
ColumnPropEnum cp;
if (colpros.TryGetValue(col, out cp)) {
switch (cp) {
case cnst_mstr_id: gm.cnst_mstr_id = s; break;
case cnst_prefix_nm: gm.cnst_prefix_nm = s; break;
...
}
}
}
}
I'm not sure at which position you add this object to a list or upload it to the server, as this is not part of the code. But it could be faster, to first check only the first column of each row if you have the necessary count of non-null values and throw an error if not and do all the other processing only if you didn't throw the error.
int rowcount = 0;
//If you need at minimum 2000 rows, you can stop after you count 2000 valid rows
for (int rw = 4; rw <= ws.Dimension.End.Row && rowcount < 2000; rw++)
{
if (ws.Cells[rw, 1].Value != null) rowcount++
}
if (rowcount < 2000) {
//throw error and return
}
//else do the list building and uploading

Highlight cells of datagridview with different values in c#

I have two datagridview.
With same column headers but different cell data.
first One is called grid_db
second one is calld grid_statement.
If the value of grid_db is not same as that of grid_statement at cell[j] i must have the cells highlighted (red).
i tried the following
int no_of_col = grid_db.Columns.Count;
int j;
for (j = 0; j < no_of_col;)
{
//if statement value is null replace with ZERO
if (grid_statement.Rows[0].Cells[j].Value != null &&
!string.IsNullOrWhiteSpace(grid_statement.Rows[0].Cells[j].Value.ToString()))
{
B = grid_statement.Rows[0].Cells[j].Value.ToString();
}
//if db value is null replace with zero
if (grid_db.Rows[0].Cells[j].Value != null &&
!string.IsNullOrWhiteSpace(grid_db.Rows[0].Cells[j].Value.ToString()))
{
A = grid_db.Rows[0].Cells[j].Value.ToString();
}
if (A != B)
{
grid_db.Rows[0].Cells[j].Style.BackColor = Color.Red;
grid_statement.Rows[0].Cells[j].Style.BackColor = Color.Red;
j++;
}
}
But it does not works.The above codes highlights ALL the columns of both grids.
Help ?
I tried your code, and it works for me, the only thing i've changed is the for loop to increment on every pass, otherwise it can easily be infinite (it only works for 1st row because that's what your code does):
public Form1()
{
InitializeComponent();
grid_db.DataSource = new[]
{
new{
id = 1,
tekst="a"
},
new
{
id=2,
tekst="b"
}
}.ToList();
grid_statement.DataSource = new[]
{
new{
id = 1,
tekst="b"
},
new
{
id=2,
tekst="c"
}
}.ToList();
Load += (sender, args) =>
{
HighlightRows();
};
}
private void HighlightRows()
{
int no_of_col = grid_db.Columns.Count;
int j;
var B = "";
var A = "";
for (j = 0; j < no_of_col; j++)
{
//if statement value is null replace with ZERO
if (grid_statement.Rows[0].Cells[j].Value != null &&
!string.IsNullOrWhiteSpace(grid_statement.Rows[0].Cells[j].Value.ToString()))
{
B = grid_statement.Rows[0].Cells[j].Value.ToString();
}
//if db value is null replace with zero
if (grid_db.Rows[0].Cells[j].Value != null &&
!string.IsNullOrWhiteSpace(grid_db.Rows[0].Cells[j].Value.ToString()))
{
A = grid_db.Rows[0].Cells[j].Value.ToString();
}
if (A != B)
{
grid_db.Rows[0].Cells[j].Style.BackColor = Color.Red;
grid_statement.Rows[0].Cells[j].Style.BackColor = Color.Red;
}
}
}
var differentCells =
grid_db.Rows.OfType<DataGridViewRow>()
.SelectMany(r=>r.Cells.OfType<DataGridViewCell>())
.Where(c=>!grid_statement[c.ColumnIndex,c.RowIndex].Value.Equals(c.Value));
//loop through all the cells and make them red
foreach(var cell in differentCells)
cell.Style.BackColor = Color.Red;

Assigning values from a cell to another

int rowsCount = 0;
//This checks to see that both textbox for items and subitems do not gain focus at the same time
if (textBoxSubItems.Text != string.Empty)
txtItems.Enabled = false;
else
txtItems.Enabled = true;
if (comboBoxItems.SelectedItem != null)
{
int idx = dataGridViewTimesheet.Rows.Add();
DataGridViewRow row = dataGridViewTimesheet.Rows[idx];
row.Cells["items"].Value = comboBoxItems.SelectedItem.ToString() + "-" + textBoxSubItems.Text;
row.Cells["fromTime"].Value = DateTime.Now.ToLongTimeString();
row.Cells["toTime"].Value = null;
row.Cells["duration"].Value = null;
row.Cells["subTotal"].Value = null;
// row.Cells["comments"].Value = "1";
}
else
MessageBox.Show("Please select an item");
string strGetColumnValue;
if (dataGridViewTimesheet.Rows.Count != 0)
rowsCount = dataGridViewTimesheet.Rows.Count;
else
MessageBox.Show("No row in the datagridview");
while (dataGridViewTimesheet.Rows.Count > 0)
{
try
{
if (dataGridViewTimesheet.CurrentRow != null)
for (int counter = 0; counter < dataGridViewTimesheet.Columns.Count; counter++)
{
if (dataGridViewTimesheet.Columns[counter].Index == 3)
{
strGetColumnValue = dataGridViewTimesheet.Rows[rowsCount].Cells[counter].Value.ToString();
dataGridViewTimesheet.Rows[rowsCount - 1].Cells[3].Value = strGetColumnValue;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
Please i have 6 columns in a datagridview, the rows are added dynamically. What i want is when rows are more than one in the datagridview it should assign the value of the second column on the current(the last row created) row to the third column of the previous row. How do I achieve this.
Try this kind of thing
int count =1;
foreach (DataGridRow row in dataGridViewTimesheet.Rows)
{
if (count % 2 == 0)
{
string secondColumn = dataGridViewTimesheet.Rows[count -1].Cells[1].ToString();
dataGridViewTimesheet.Rows[count].Cells[2].Value = secondColumn;
}
count++;
}

Categories

Resources