简体   繁体   中英

Why is my foreach loop only processing every other one?

I have a button on my Win Form that goes through each row in my DataGridView and calls another function as needed.

The loop and function work, however, it seems to be skipping rows. I selected 6 rows in a row, and only 3 got processed (at the end of the function it removes the row, only 3 of the 6 were removed).

I confirmed that the loop only calls the function 3 of the 6 times, so the issues appears to be the loop.

What in the world is going on?

Here is the button code:

 private void btnProcess_Click(object sender, EventArgs e) {

        foreach (DataGridViewRow row in gridData.Rows) {

            // If Approved to Buy
            if (Convert.ToBoolean(row.Cells["Approve"].EditedFormattedValue) == true) {
                if (row.Cells["Action"].Value.ToString() == "Buy") {
                    ApprovedBuyAction(row.Index);
                }
            }
        }

    }  // End btnPurchase Function

And here is the function being called:

private void ApprovedBuyAction(int rowNum) {
    using (SqlConnection conn = new SqlConnection(Global.connString)) {
        conn.Open();

        string sqlInsertQuery = "INSERT INTO PORG_Process " +
                    "(SessionID, Date, Type, BuyerID, SourceLoc, VendorID, SupplierID, Item, UOM, Qty, processFlag, deleteFlag, computerName, userName) VALUES " +
                    "(@SessionID, @Date, @Type, @BuyerID, @LocationID, @VendorID, @SupplierID, @Item, @UOM, @Qty, @processFlag, @deleteFlag, @computerName, @userName)";

        using (SqlCommand sqlInsert = new SqlCommand(sqlInsertQuery, conn)) {
            sqlInsert.Parameters.Add("@SessionID", SqlDbType.VarChar, 60).Value = hdnSessionID.Text;
            sqlInsert.Parameters.Add("@Date", SqlDbType.VarChar, 60).Value = DateTime.Now.ToString();
            sqlInsert.Parameters.Add("@Type", SqlDbType.VarChar, 10).Value = "PO";
            sqlInsert.Parameters.Add("@BuyerID", SqlDbType.VarChar, 60).Value = hdnBuyerID.Text;
            sqlInsert.Parameters.Add("@LocationID", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["Location"].Value;
            sqlInsert.Parameters.Add("@VendorID", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["Vendor"].Value;
            sqlInsert.Parameters.Add("@SupplierID", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["Supplier"].Value;
            sqlInsert.Parameters.Add("@Item", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["Item"].Value;
            sqlInsert.Parameters.Add("@UOM", SqlDbType.VarChar, 10).Value = gridData.Rows[rowNum].Cells["UOM"].Value;
            sqlInsert.Parameters.Add("@Qty", SqlDbType.VarChar, 60).Value = gridData.Rows[rowNum].Cells["FinalQty"].Value;
            sqlInsert.Parameters.Add("@processFlag", SqlDbType.Int).Value = 1;
            sqlInsert.Parameters.Add("@deleteFlag", SqlDbType.Int).Value = 0;
            sqlInsert.Parameters.Add("@computerName", SqlDbType.VarChar, 60).Value = hdnMachineName.Text;
            sqlInsert.Parameters.Add("@userName", SqlDbType.VarChar, 60).Value = hdnUserName.Text;

            sqlInsert.CommandType = CommandType.Text;
            sqlInsert.ExecuteNonQuery();
        }

        string sqlUpdateQuery = "UPDATE PorgReqs SET processFlag = 1 WHERE id = @TableID";

        using (SqlCommand sqlUpdate = new SqlCommand(sqlUpdateQuery, conn)) {
            sqlUpdate.Parameters.Add("@TableID", SqlDbType.Int).Value = gridData.Rows[rowNum].Cells["id"].Value;

            sqlUpdate.CommandType = CommandType.Text;
            sqlUpdate.ExecuteNonQuery();
        }
    }

    // Remove the Row from the Grid
    gridData.Rows.RemoveAt(rowNum);
} // End ApprovedBuyAction Function

This is the culprit, I believe, down at the end of ApprovedBuyAction() :

gridData.Rows.RemoveAt(rowNum);

It looks like you're removing rows from the collection as you iterate over it: You delete item 0, then the former item 1 is now item zero. Next iteration, you move on to the new item 1, formerly item 2.

I'm surprised you got away with that without an exception.

I suggest that in your loop through dataGrid.Rows , just make a list of the row indexes you want to delete. After that loop ends, loop through the list of indexes, passing each one to ApprovedBuyAction(idx); in turn.

You could also iterate through a temporary copy of the collection:

foreach (var row in gridData.Rows.Cast<DataGridViewRow>().ToList())
{

...or in reverse, as Rufus suggests. I prefer to avoid for loops when foreach is an option, because when there's no index, I don't have to think about the index. But for loops are hardly rocket science, even for me. Any of the three approaches will work.

The problem here appears that you're removing rows from the data grid as you iterate over them. In general, if you want to remove items from a collection while you iterate over it, you should start from the last index and move towards the first, to avoid sitations like this (when you remove an item, all the following items' indexes will change by -1 ).

For example, you could replace your foreach code with the following to walk backwards through the rows:

private void btnProcess_Click(object sender, EventArgs e) 
{
    for (int i = gridData.Rows.Count - 1; i > -1; i--)
    {
        DataGridViewRow row = gridData.Rows[i];

        // rest of code remains unchanged
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM