Test for an empty DataRow in C# - c#

what I'm trying to do: I have a large datatable, and I'm going through a list of strings where some of them are in the datatable and some aren't. I need to make a list of those that are, and count those that aren't.
This is my code part:
DataRow[] foundRows;
foundRows = DTgesamt.Select("SAP_NR like '%"+SAP+"%'");
if (AreAllCellsEmpty(foundRows[0]) == false && !(foundRows[0]==null))
{
list.Add(SAP);
}
else
{
notfound++;
}
public static bool AreAllCellsEmpty(DataRow row)
{
if (row == null) throw new ArgumentNullException("row");
for (int i = row.Table.Columns.Count - 1; i >= 0; i--)
{
if (!row.IsNull(i))
{
return false;
}
}
return true;
}
DTgesamt ist a large DataTable. "SAP" is a string that is in the first column of the DataTable, but not all of them are included. I want to count the unfound ones with the int "notfound".
The problem is, the Select returns an empty DataRow {System.Data.DataRow[0]} when it finds nothing.
I'm getting the errormessage Index out of array area.
The two statements in the if-clause are what I read on the internet but they don't work. With only the 2nd statement it just adds all numbers to the list, with the first it still gives this error.
Thanks for any help :)

check count of items in foundRows array to avoid IndexOutOfRange exception
foundRows = DTgesamt.Select("SAP_NR like '%"+SAP+"%'");
if (foundRows.Length > 0 && AreAllCellsEmpty(foundRows[0])==false)
list.Add(SAP);
else
notfound++;

The found cells cannot be empty. Your select statement would be wrong. So what you actually need is:
if (DTgesamt.Select("SAP_NR like '%"+SAP+"%'").Any())
{
list.Add(SAP);
}
else
{
notfound++;
}
You probably don't even need the counter, when you can calculate the missed records based on how many SAP numbers you had and how many results you got in list.
If you have an original list or array of SAP numbers, you could shorten your whole loop to:
var numbersInTable = originalNumbers.Where(sap => DTgesamt.Select("SAP_NR like '%"+sap+"%'").Any()).ToList();
var notFound = originalNumbers.Count - numbersInTable.Count;

Related

Comparing two DataGridViews and deleting the values of the second if exists in the first one

I am doing a code where I compare two columns of DGV roles, the first DGV (DGV1) has the raw data with duplicate roles, and the second DGV (DGV4) is a dictionary with all existing roles (no duplicates), it has to go to each row of the dictionary and if the role exists in the DGV1, it should be removed from the dictionary, leaving only the roles in the dictionary that are not currently being used in the raw data. My code is removing the roles, but when the dictionary has a value that doesn't exist in DGV1, it stops working (DGV1 continues to loop until it has an index error). Any suggestion?
NOTE: The rows in the dictionary automatically go to the first index, so there is no need to increment int i.
int eliminado = 0;
int filasDGV1 = dataGridView1.Rows.Count;
int filasDGV4 = dataGridView4.Rows.Count;
int i = 0;
int j = 0;
do
{
string perfilVacio = dataGridView4["GRANTED_ROLE", i].Value.ToString();
string perfiles = dataGridView1["GRANTED_ROLE", j].Value.ToString();
if(perfiles != perfilVacio)
{
j++;
}
else if(perfiles == perfilVacio)
{
dataGridView4.Rows.RemoveAt(i);
}
}
while (eliminado <= filasDGV4);
The first excel is DGV1 and the other is DGV2, I highlighted where is the code looping currently
The orange highlight is where the loop change in DGV1 but in the dictionary doesnt exist so its stuck there
Change your loop condition to include a test for the changing index j and also to check whether there are rows left to be eliminated.
int filasDGV1 = dataGridView1.Rows.Count;
int j = 0;
while (j < filasDGV1 && dataGridView4.Rows.Count > 0)
{
string perfilVacio = dataGridView4["GRANTED_ROLE", 0].Value.ToString();
string perfiles = dataGridView1["GRANTED_ROLE", j].Value.ToString();
if(perfiles == perfilVacio)
{
dataGridView4.Rows.RemoveAt(0);
}
else
{
j++;
}
}
If you test perfiles != perfilVacio in if you don't have to test perfiles == perfilVacio in else if, because this automatically the case. Either they are equal or they are not. There no other possibility.
Also, it is generally more readable if you ask a positive question in if like == instead of a negative one like !=.
Since i is always 0 I replaced it by the constant 0. The variable eliminado is not required (unless it is incremented when rows are removed to display the number of deleted rows).
The number of rows in dataGridView4 should not be stored in filasDGV4 as this number changes.
Update
According to your comments and the new screenshots, you need two loops. (The code above only works if both lists are sorted). We could use two nested loops; however, this is slow. Therefore, I suggest collecting the unwanted roles in a HashSet<string> first. Testing whether an item is in a HashSet is extremely fast. Then we can loop through the rows of the dictionary and delete the unwanted ones.
var unwanted = new HashSet<string>();
for (int i = 0; i < dataGridView1.Rows.Count: i++)
{
unwanted.Add(dataGridView1["GRANTED_ROLE", i].Value.ToString());
}
int row = 0;
while (row < dataGridView4.Rows.Count)
{
string perfilVacio = dataGridView4["GRANTED_ROLE", row].Value.ToString();
if(unwanted.Contains(perfilVacio))
{
dataGridView4.Rows.RemoveAt(row);
}
else
{
row++;
}
}
Suggestion: Using data binding to bind your DataGridViews to generic lists would enable you to work on these lists instead of working on the DGVs. This would simplify the data handling considerably.

DataRow.SetField() gives a null ref exception when adding data to a column I previously deleted then added back

UPDATE
I think I have found what is causing the issue here https://stackoverflow.com/a/5665600/19393524
I believe my issue lies with my use of .DefaultView. The post thinks when you do a sort on it it is technically a write operation to the DataTable object and might not propagate changes made properly or entirely. It is an interesting read and seems to answer my question of why passing valid data to a DataRow is throwing this exception AFTER I make changes to the datatable
UPDATE:
Let me be crystal clear. I have already solved my problem. I would just like to know why it is throwing an error. In my view the code should work and it does.. the first run through.
AFTER I have already deleted the column then added it back (run this code once)
When I debug my code line by line in Visiual studio and stop at the line:
data.Rows[i].SetField(sortColumnNames[k], value);
the row exists
the column exisits
value is not null
sortColumnNames[k] is not null and contains the correct column name
i is 0
Yet it still throws an exception. I would like to know why. What am I missing?
Sorry for the long explanation but this one needs some context unfortunately.
So my problem is this, I have code that sorts data in a DataTable object by column. The user picks the column they want to sort by and then my code sorts it.
I ran into an issue where I needed numbers to sort as numbers not strings (all data in the table is strings). eg (string sorting would result in 1000 coming before 500)
So my solution was to create a temporary column that uses the correct datatype so that numbers get sorted properly and the original string data of the number remains unchanged but is now sorted properly. This worked perfectly. I could sort string numeric data as numeric data without changing the formatting of the number or data type.
I delete the column I used to sort afterwards because I use defaultview to sort and copy data to another DataTable object.
That part all works fine the first time.
The issue is when the user needs to do a different sort on the same column. My code adds back the column. (same name) then tries to add values to the column but then I get a null reference exception "Object not set to an instance of an object"
Here is what I've tried:
I've tried using AcceptChanges() after deleting a column but this did nothing.
I've tried using column index, name, and column object returned by DataTable.Columns.Add() in the first parameter of SetField() in case it was somehow referencing the "old" column object I deleted (this is what I think the problem is more than likely)
I've tried changing the value of the .ItemArray[] directly but this does not work even the first time
Here is the code:
This is the how the column names are passed:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
data.DefaultView.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = data.DefaultView.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
This is where the temp columns are added:
private string[] AddSortColumns(string[] items)//adds columns to data that will be used to sort
//(ensures numbers are sorted as numbers and strings are sorted as strings)
{
string[] sortColumnNames = new string[items.Length];
for (int k = 0; k < items.Length; k++)
{
int indexOfOrginialColumn = Array.IndexOf(columns, items[k]);
Type datatype = CheckDataType(indexOfOrginialColumn);
if (datatype == typeof(double))
{
sortColumnNames[k] = items[k] + "Sort";
data.Columns.Add(sortColumnNames[k], typeof(double));
for (int i = 0; i < data.Rows.Count; i++)
{
//these three lines add the values in the original column to the column used to sort formated to the proper datatype
NumberStyles styles = NumberStyles.Any;
double value = double.Parse(data.Rows[i].Field<string>(indexOfOrginialColumn), styles);
bool test = data.Columns.Contains("QtySort");
data.Rows[i].SetField(sortColumnNames[k], value);//this is line that throws a null ref exception
}
}
else
{
sortColumnNames[k] = items[k];
}
}
return sortColumnNames;
}
This is the code that deletes the columns afterward:
private void RemoveSortColumns(string[] columnsToRemove)
{
for (int i = 0; i < columnsToRemove.Length; i++)
{
if (columnsToRemove[i].Contains("Sort"))
{
sortedData.Columns.Remove(columnsToRemove[i]);
}
}
}
NOTE:
I've been able to fix the problem by just keeping the column in data and just deleting the column from sortedData as I use .Clear() on the sorted table which seems to ensure the exception is not thrown.
I would still like an answer though as to why this is throwing an exception. If I use .Contains() on the line right before the one where the exception is thrown is says the column exists and returns true and in case anyone is wondering the params sortColumnNames[k] and value are never null either.
Your problem is probably here:
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
}
}
}
If you have 2 columns, and the first one matches the if, you will never look at the second.
This is because it will run:
i = 0
is i < columns.Count which is 2 => yes
is col[0].Contains("sort") true => yes
remove col[0]
i = 1
is i < columns.Count which is 1 => no
The solution is to readjust i after the removal
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
i--;//removed 1 element, go back 1
}
}
}
I fixed my original issue by changing a few lines of code in my SortByColumn() method:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
DataView userSelectedSort = data.AsDataView();
userSelectedSort.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = userSelectedSort.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
Instead of sorting on data.DefaultView I create a new DataView object and pass data.AsDataView() as it's value then sort on that. Completely gets rid of the issue in my original code. For anyone wondering I still believe it is bug with .DefaultView in the .NET framework that Microsoft will probably never fix. I hope this will help someone with a similar issue in the future.
Here is the link again to where I figured out a solution to my problem.
https://stackoverflow.com/a/5665600

Get last column in Google Sheets API v4 in c#

I have seen many posts about finding the last row of a given column for Google Sheets API v4 in C#, but I can't seem to find anything about finding the last column of a given row. I didn't find any questions about this specifically - but if I'm mistaken please direct me to the right place.
In my sheet, I have headers for each column. Over time, I anticipate I will need to add or remove columns as needed - it would be great to not have to update my code every time this happens.
I'm at the beginning stages of writing my code that gathers my data from Google Sheets - but here is what I have so far. I know that I will need to change the way my variable "range" is written, just don't know what.
static void ReadEntries()
{
var range = $"{sheet}!A1:ET";
var request = service.Spreadsheets.Values.Get(SpreadsheetId, range);
var response = request.Execute();
var values = response.Values;
if(values != null && values.Count>0)
{
foreach (var row in values)
{
System.Diagnostics.Debug.WriteLine("{0} | {1} | {2}", row[0], row[1], row[2]);
}
}
else
{
System.Diagnostics.Debug.WriteLine("No data found.");
}
}
EDIT: SOLVED
I used the pseudo code provided by Nazi A for this. I was having issues with the if(row[col]) piece with casting and other system exceptions. It turns out foreach allows for us to not have to check if that row[col] is in range. Below is my final code in case anyone needs it in the future. I plan to let column "ET" declared in var range = $"{sheet}!A1:ET; be big enough to accommodate any future columns being added to my spreadsheet. Thanks for your help!
static void ReadEntries()
{
var range = $"{sheet}!A1:ET";
var request = service.Spreadsheets.Values.Get(SpreadsheetId, range);
var response = request.Execute();
var values = response.Values;
int max = 0;
int currMax;
if (values != null && values.Count>0)
{
foreach(var row in values)
{
currMax = 0;
foreach(var col in row)
{
currMax++;
}
if (max < currMax)
{
max = currMax;
}
}
}
else
{
System.Diagnostics.Debug.WriteLine("No data found.");
}
System.Diagnostics.Debug.WriteLine(max);
}
So basically, you need to have a nested loop to traverse all rows and columns in values.
I have tested this psuedo code and worked but since I have no any means to run a C# code, this is all I can give to you. This is a pseudo code that should be readable to you.
var max = 0;
foreach(var row in values){
var currMax = 0;
foreach(var col in row){
if(row[col]){ // as long as data exists, currMax will increment
currMax++;
continue;
}
break; // stop loop if last cell being checked is empty
}
if(max < currMax){ // assign the largest currMax to max
max = currMax;
}
}
So in this psuedo code, max will contain the value of the largest column of all rows in the range. this code above should replace your foreach call
If you have any questions, feel free to clarify below.

Delete Multiple rows from datagridview

I've a datagridview in which values are inserted.The gridview is like this.
Item PRID
------ ------
Item1 1
Item2 2
Item3 2
I am trying to compare the PRID with a variable which holds the selected row PRID.
What I've done so far.
foreach (DataGridViewRow dgv_r in PurchaseOrder_dgv.Rows)
{
if (dgv_r.Cells[1].Value.ToString() == CurrentSelected_PRID_ForPurchaseOrder.ToString())
{
PurchaseOrder_dgv.Rows.Remove(dgv_r);
}
}
But it deletes the bottom row not the second row.and gives the following error.What I want is if the value of CurrentSelected_PRID_ForPurchaseOrder is equal to 2 then it should delete both the rows.I've tried it using for loop also but it gives me Index out of range error.It is giving the following error.
Object Reference Not set to an instance of object
The are a couple of ways around this. One is to do the following
for (int i = dataGridView1.RowCount - 1; i >= 0; i--)
if (String.Compare(dataGridView1.Rows[i].Cells[1].Value.ToString(), "2") == 0)
dataGridView1.Rows.Remove(dataGridView1.Rows[i]);
This is looping from the bottom end of the DataGridView and avoids the problem with removing rows whilst iterating.
I hope this helps.
As mellamokb pointed out the reason is because your editing the collection during the foreach. One solution would be to store the rows with the same PRID in a list first and then remove them after. Something like this
var rowsToRemove = new List<int>();
foreach (DataGridViewRow dgv_r in PurchaseOrder_dgv.Rows)
{
if (dgv_r.Cells[1].Value.ToString() == CurrentSelected_PRID_ForPurchaseOrder.ToString())
{
rowsToRemove.Add(dgv_r.Index);
}
}
rowsToRemove.Reverse();
rowsToRemove.ForEach(i => PurchaseOrder_dgv.Rows.RemoveAt(i));
Another solution is to use a while loop instead which takes into account the possiblity of the collection size changing.
int i = 0;
while (i < PurchaseOrder_dgv.Rows.Count)
{
if (PurchaseOrder_dgv.Rows[i].Cells[1].Value.ToString() == CurrentSelected_PRID_ForPurchaseOrder.ToString())
{
PurchaseOrder_dgv.Rows.RemoveAt(i);
}
i++;
}

compare value property

how can I compare value property of item to datatable column called Value? Please help me with the syntax
if ((String)item.Value.IndexOf((string)results("value") Stringcomparison.CurrentCultureIgnoreCase) > -1)
{
returnItems.Add(item);
}
Your question and comment seem to be asking different things, but in both cases the answer is probably to unpick the big long line of code into explanatory variables:
string valueFromTable = currentRow["Value"].ToString();
bool itemValueContainsValueFromTable = item.Value.IndexOf(valueFromTable, StringComparison.CurrentCultureIgnoreCase) >= 0;
bool itemValueEqualsValueFromTable = item.Value.Equals(valueFromTable, StringComparison.CurrrentCultureIgnoreCase);
if (/* whichever of these you are interested in */)
{
returnItems.Add(item);
}
(ignoring error cases here, specifically if item.Value is null)
Note that to get a value from the DataTable you will need to pick a row. If you want to see if the item matches any row, then iterate over the rows (foreach (var row in table.Rows)).

Categories