DataGridViewRow单元格计数引发ArgumentOutOfRangeException问题排查
问题解决:ArgumentOutOfRangeException异常分析与修复
问题现象
运行代码时触发ArgumentOutOfRangeException异常,报错时DataGridViewRow.Cells.Count为15(即表格有15列),但表格仅有5行数据,程序在i=5时停止运行。
核心错误原因
代码存在变量混淆:外层foreach遍历DataGridView的每一行(rw),但内层for循环的变量i是用来遍历当前行的列索引,却被错误地当作dataGridView1.Rows的行索引使用。当i增长到5时,超过了表格的最大行索引(5行数据的索引范围是0-4),访问dataGridView1.Rows[5]直接触发越界异常。
同时代码还存在其他潜在问题:
- 逻辑判断错误:
if (rw.Cells[i].Value != null || rw.Cells[i].Value != DBNull.Value || !String.IsNullOrEmpty(rw.Cells[i].Value.ToString()))用||会导致条件永远为true,应该改为&&,否则只要有一个条件满足就会执行分支,比如Value为DBNull时,调用ToString()会直接报错。 - SQL注入风险:直接拼接SQL语句,未使用参数化查询,存在安全隐患且易因特殊字符导致语法错误。
- 数据库连接管理混乱:循环中频繁打开关闭连接,部分分支关闭连接后可能重复执行关闭操作。
- 数据转换无安全校验:直接使用
Convert.ToDouble、Convert.ToDateTime,单元格值无效时会触发异常。
修复步骤
- 修正行索引引用:将所有
dataGridView1.Rows[i]替换为当前遍历的行rw,业务逻辑是处理整行数据,不需要用列索引访问行。 - 修复逻辑判断条件:把
||改为&&,确保单元格值不为null、不为DBNull,且转换为字符串后不为空。 - 优化连接管理:用
using语句自动管理数据库连接,避免手动关闭的混乱。 - 替换参数化SQL:消除SQL注入风险,提升代码稳定性。
- 添加安全转换:使用
TryParse系列方法进行数据转换,防止无效值引发异常。
修正后的代码示例
// 用using自动管理数据库连接,避免手动关闭的问题 using (SqlConnection commcon = new SqlConnection("你的数据库连接字符串")) { commcon.Open(); foreach (DataGridViewRow rw in this.dataGridView1.Rows) { // 跳过DataGridView自动生成的空行 if (rw.IsNewRow) continue; object accountCellValue = rw.Cells[1].Value; // 正确的非空判断逻辑 if (accountCellValue != null && accountCellValue != DBNull.Value && !string.IsNullOrEmpty(accountCellValue.ToString())) { int obaccountid = 0; double obtotal = 0; int billno = 0; int accountid = 0; string accountno = accountCellValue.ToString(); string accountname = ""; double total = 0; int cellnumber = 0; DateTime colldate; // 安全转换账号ID if (!int.TryParse(getAccountID(accountno), out accountid)) { MessageBox.Show($"账号{accountno}格式无效", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); continue; } billno = getBillno(accountid); accountname = getProperAccountName(accountid); // 安全转换金额 if (!double.TryParse(rw.Cells[3].Value?.ToString(), out total)) { MessageBox.Show($"第{rw.Index+1}行金额格式错误", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); continue; } // 安全转换手机号 if (!int.TryParse(rw.Cells[14].Value?.ToString(), out cellnumber)) { MessageBox.Show($"第{rw.Index+1}行手机号格式错误", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); continue; } // 安全转换日期 if (!DateTime.TryParse(rw.Cells[0].Value?.ToString(), out colldate)) { MessageBox.Show($"第{rw.Index+1}行日期格式错误", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); continue; } // 参数化SQL,避免注入风险 string sql = @"SELECT TOP 1 total, accountid FROM [bicos-ob].dbo.[Outstanding Bills] WHERE accountid = @AccountId AND MONTH([billing date]) = MONTH(@CollDate) AND YEAR([billing date]) = YEAR(@CollDate) ORDER BY id desc"; using (SqlDataAdapter a = new SqlDataAdapter(sql, commcon)) { a.SelectCommand.Parameters.AddWithValue("@AccountId", accountid); a.SelectCommand.Parameters.AddWithValue("@CollDate", colldate); DataTable t = new DataTable(); a.Fill(t); if (t.Rows.Count > 0) { // 安全转换未结账单数据 if (!double.TryParse(t.Rows[0]["total"].ToString(), out obtotal)) { MessageBox.Show("未结账单金额格式错误", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); continue; } if (!int.TryParse(t.Rows[0]["accountid"].ToString(), out obaccountid)) { MessageBox.Show("未结账单账号ID格式错误", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); continue; } // 执行对应业务逻辑 if (obtotal == total && obaccountid == accountid) { using (SqlCommand cmd = new SqlCommand("[save__collection_RDPayments]", commcon)) { cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@billno", billno); cmd.Parameters.AddWithValue("@accountid", accountid); cmd.Parameters.AddWithValue("@accountno", accountno); cmd.Parameters.AddWithValue("@accountname", accountname); cmd.Parameters.AddWithValue("@or", ORNumber); cmd.Parameters.AddWithValue("@collectiondate", colldate); cmd.Parameters.AddWithValue("@paymenttype", paymenttype); cmd.Parameters.AddWithValue("@checkno", checkno); cmd.Parameters.AddWithValue("@overpayment", 0); cmd.Parameters.AddWithValue("@partialpayment", 0); cmd.Parameters.AddWithValue("@userid", getUserID(user)); cmd.Parameters.AddWithValue("@RefNo", rw.Cells[7].Value?.ToString() ?? ""); cmd.ExecuteNonQuery(); } appendFiles($"已入账款项: {accountno} {accountid} {accountname}"); InsertAuditLog(commcon, colldate, accountno, accountname, "已入账", cellnumber); } else if (obtotal < total && obaccountid == accountid) { InsertAuditLog(commcon, colldate, accountno, accountname, "超额付款", cellnumber); } else if (obtotal > total && obaccountid == accountid) { InsertAuditLog(commcon, colldate, accountno, accountname, "付款金额小于账单金额", cellnumber); } else if (obaccountid != accountid) { InsertAuditLog(commcon, colldate, accountno, accountname, "账号不匹配", cellnumber); } else { InsertAuditLog(commcon, colldate, accountno, accountname, "未知状态", cellnumber); } } else { MessageBox.Show($"第{rw.Index+1}行未找到对应未结账单数据!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } } else { MessageBox.Show($"第{rw.Index+1}行账号不能为空!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } } } // 提取审计日志插入逻辑,减少代码重复 private void InsertAuditLog(SqlConnection conn, DateTime collDate, string accountNo, string accountName, string remarks, int cellNumber) { string sql = @"INSERT INTO CodeAventus_ePostingPortal.dbo.[audit_cr_log] (collectiondate, accountno, accountname, remarks, transactiondate, cellphonenumber) VALUES(@collectiondate, @accountno, @accountname, @remarks, @transactiondate, @cellphonenumber)"; using (SqlCommand cmdlog = new SqlCommand(sql, conn)) { cmdlog.Parameters.AddWithValue("@collectiondate", collDate); cmdlog.Parameters.AddWithValue("@accountno", accountNo); cmdlog.Parameters.AddWithValue("@accountname", accountName); cmdlog.Parameters.AddWithValue("@remarks", remarks); cmdlog.Parameters.AddWithValue("@transactiondate", DateTime.Now); cmdlog.Parameters.AddWithValue("@cellphonenumber", cellNumber); cmdlog.ExecuteNonQuery(); } }
额外说明
- 原代码内层
for循环遍历列是错误逻辑,业务是处理整行数据,因此直接移除该循环,改为处理当前行rw。 - 添加
rw.IsNewRow判断,跳过DataGridView自动生成的空行,避免处理无效数据。 - 提取
InsertAuditLog方法,减少重复代码,提升可维护性。 - 所有数据转换使用
TryParse,确保代码不会因无效值崩溃。
内容的提问来源于stack exchange,提问作者letsjigsaw
相关产品推荐
相关产品推荐

