C#对比两个DataGridView单元格值仅插入差异行问题排查
问题背景
项目中使用2个DataGridView控件,分别为承载新结果数据的dgvResult、承载旧结果数据的dgvresultold,业务需求为:仅当dgvResult某行第3列(索引为3)的单元格值,与dgvresultold同索引行第3列的单元格值不相等时,才将dgvResult对应行的数据插入数据库。
初始版本代码运行时存在问题:点击保存按钮后所有行都会被存入数据库,不符合仅插入差异行的要求;调整对比逻辑后又出现完全无法插入数据的故障。
现有代码片段
保存按钮初始版本代码
private void btnSave_Click(object sender, EventArgs e) { for (int i = 0; i < dgvResult.Rows.Count; i++) { if (!String.IsNullOrEmpty(dgvResult.Rows[i].Cells[3].Value.ToString())) { if (dgvResult.Rows[i].Cells[3].Value.ToString() != dgvresultold.Rows[i].Cells[3].Value.ToString()) { result.UPDATED_RESULTS(Convert.ToInt32(txtOrder.Text), Convert.ToInt32(dgvResult.Rows[i].Cells[0].Value), txtApprovedby.Text, DateTime.Parse(dateTimeApprove.Value.ToString()), dgvResult.Rows[i].Cells[3].Value.ToString(), Convert.ToInt32(dgvResult.Rows[i].Cells[4].Value.ToString()), Convert.ToInt32(txtMRN.Text), Convert.ToInt32(textCustId.Text), txtApprovedby.Text, DateTime.Parse(dateTimeApprove.Value.ToString()), 1, Convert.ToInt32(dgvResult.Rows[i].Cells[7].Value), dgvResult.Rows[i].Cells[8].Value.ToString()); } } }
初始版本使用的值对比逻辑如下,预期仅命中值存在差异的行:
if (dgvResult.Rows[i].Cells[3].Value.ToString() != dgvresultold.Rows[i].Cells[3].Value.ToString())
数据插入存储过程代码
ALTER proc [dbo].[UPDATED_RESULTS] @ORDER_ID int, @TESTID int, @APPROVED_BY varchar(50), @APPROVED_DATE datetime, @RESULT_NUMBER varchar(50), @MACHINE_ID int, @patient_no int, @custid int, @CORRECTED_BY varchar(50), @CORRECTED_DATE datetime, @messageid int, @deptid int, @REQ_FORM_NO varchar(50) AS INSERT INTO [dbo].[LAB_RESULTS_UPDATED] ([TESTID],[ORDER_ID],[APPROVED_BY],[APPROVED_DATE],[RESULT_NUMBER],[machine_id],[patient_no],[custid],[CORRECTED_BY],[CORRECTED_DATE] ,[messageid],deptid,REQ_FORM_NO) values (@ORDER_ID,@TESTID,@APPROVED_BY,@APPROVED_DATE,@RESULT_NUMBER,@MACHINE_ID,@patient_no,@custid,@CORRECTED_BY,@CORRECTED_DATE, @messageid,@deptid,@REQ_FORM_NO)
调整后失效的循环代码
调整循环边界、改用String.Equals做对比后,代码完全无法执行插入操作,对应循环片段如下:
for (int i = 0; i < dgvResult.Rows.Count-1; i++) { if (!String.IsNullOrEmpty(dgvResult.Rows[i].Cells[3].Value.ToString())) { if (!String.Equals(dgvResult.Rows[i].Cells[3].Value.ToString() , dgvresultold.Rows[i].Cells[3].Value.ToString())) { result.UPDATED_RESULTS(Convert.ToInt32(txtOrder.Text), Convert.ToInt32(dgvResult.Rows[i].Cells[0].Value), txtApprovedby.Text, DateTime.Parse(dateTimeApprove.Value.ToString()), dgvResult.Rows[i].Cells[3].Value.ToString(), Convert.ToInt32(dgvResult.Rows[i].Cells[4].Value.ToString()), Convert.ToInt32(txtMRN.Text), Convert.ToInt32(textCustId.Text), txtApprovedby.Text, DateTime.Parse(dateTimeApprove.Value.ToString()), 1, Convert.ToInt32(dgvResult.Rows[i].Cells[7].Value), dgvResult.Rows[i].Cells[8].Value.ToString()); } } }
故障原因排查
- 空值处理缺失:代码直接对单元格的
Value属性调用ToString(),如果dgvresultold对应单元格值为null或DBNull,会直接抛出空引用异常,中断整个保存流程,这是调整后完全无法插入数据的核心原因。 - 新行处理逻辑错误:DataGridView默认开启
AllowUserToAddRows属性时,会在列表末尾自动生成一行空白的用户编辑行,硬编码Rows.Count-1虽然能跳过这行,但如果后续关闭该属性,会导致最后一行有效数据被漏处理;初始版本没有跳过这行时,空白行的空值和旧表值对比必然不等,会触发无意义插入。 - 值对比未做归一化:如果单元格值首尾存在不可见空格、存储格式不一致(比如数值转字符串后小数位不同、大小写差异),会导致视觉上相等的值被判定为不等,触发全量插入。
- 缺少行数一致性校验:如果两个DataGridView加载的有效数据行数不一致,按同索引对比会出现索引越界、行数据匹配错误的问题。
修复后的完整实现代码
private void btnSave_Click(object sender, EventArgs e) { // 动态计算两个表的有效数据行数,自动跳过末尾的用户新增空白行 int newValidRowCount = dgvResult.Rows.Count - (dgvResult.AllowUserToAddRows ? 1 : 0); int oldValidRowCount = dgvresultold.Rows.Count - (dgvresultold.AllowUserToAddRows ? 1 : 0); // 行数不匹配时直接终止流程,避免索引越界 if (newValidRowCount != oldValidRowCount) { MessageBox.Show("新旧结果数据行数不匹配,无法执行保存操作"); return; } for (int i = 0; i < newValidRowCount; i++) { // 安全获取新值,统一处理null/DBNull情况,同时去除首尾空格做归一化 string newResult = dgvResult.Rows[i].Cells[3].Value?.ToString().Trim() ?? string.Empty; // 新值为空则跳过当前行 if (string.IsNullOrEmpty(newResult)) continue; // 安全获取旧值 string oldResult = dgvresultold.Rows[i].Cells[3].Value?.ToString().Trim() ?? string.Empty; // 仅当新旧值不相等时执行插入,可根据业务需要调整是否忽略大小写 if (!string.Equals(newResult, oldResult, StringComparison.Ordinal)) { result.UPDATED_RESULTS( orderId: Convert.ToInt32(txtOrder.Text), testId: Convert.ToInt32(dgvResult.Rows[i].Cells[0].Value), approvedBy: txtApprovedby.Text, approveDate: DateTime.Parse(dateTimeApprove.Value.ToString()), resultNumber: newResult, machineId: Convert.ToInt32(dgvResult.Rows[i].Cells[4].Value.ToString()), patientNo: Convert.ToInt32(txtMRN.Text), custId: Convert.ToInt32(textCustId.Text), correctedBy: txtApprovedby.Text, correctedDate: DateTime.Parse(dateTimeApprove.Value.ToString()), messageId: 1, deptId: Convert.ToInt32(dgvResult.Rows[i].Cells[7].Value), reqFormNo: dgvResult.Rows[i].Cells[8].Value.ToString() ); } } }
修复点说明
- 采用空值传播运算符
?.和空值合并运算符??处理单元格取值,彻底避免空引用异常 - 动态计算有效行数,兼容
AllowUserToAddRows属性开关的两种场景,不会漏处理数据也不会读取空白新增行 - 对比前对字符串做Trim处理,消除首尾空格导致的对比误判,可根据业务需要调整字符串对比规则
- 增加前置行数校验,从根源上避免索引越界问题
内容的提问来源于stack exchange,提问作者MarwanAbu
相关产品推荐
相关产品推荐

