当DataTable/DataGridView列值变化时添加分组求和行
实现DataGridView按Unidad分组插入求和行的解决方案
一、先修复SQL注入风险
原代码通过字符串拼接生成SQL语句,存在严重的SQL注入漏洞,必须改为参数化查询,以下是修改后的查询代码:
string mostrarTurnos = @"SELECT * FROM TablaSalidas WHERE noAdmin = @noAdmin AND MONTH(fecha) = @mes AND YEAR(fecha) = @year ORDER BY noUnidad ASC, fecha"; SqlDataAdapter da = new SqlDataAdapter(mostrarTurnos, sc); da.SelectCommand.Parameters.AddWithValue("@noAdmin", recaudacion); da.SelectCommand.Parameters.AddWithValue("@mes", mes); da.SelectCommand.Parameters.AddWithValue("@year", year); DataTable dt = new DataTable(); da.Fill(dt);
同时,原有的总计查询也需要改为参数化,避免重复打开关闭连接:
// 统一获取整体总计,用一个查询代替三次 string totalQuery = @"SELECT SUM(importeSalida) as TotalSalida, SUM(importeRuta) as TotalRuta, SUM(importeDevengado) as TotalDevengado FROM TablaSalidas WHERE noAdmin = @noAdmin AND MONTH(fecha) = @mes AND YEAR(fecha) = @year"; SqlCommand cmdTotal = new SqlCommand(totalQuery, sc); cmdTotal.Parameters.AddWithValue("@noAdmin", recaudacion); cmdTotal.Parameters.AddWithValue("@mes", mes); cmdTotal.Parameters.AddWithValue("@year", year); sc.Open(); SqlDataReader drTotal = cmdTotal.ExecuteReader(); if (drTotal.Read()) { total = drTotal.IsDBNull(drTotal.GetOrdinal("TotalSalida")) ? 0 : drTotal.GetDecimal(drTotal.GetOrdinal("TotalSalida")); total1 = drTotal.IsDBNull(drTotal.GetOrdinal("TotalRuta")) ? 0 : drTotal.GetDecimal(drTotal.GetOrdinal("TotalRuta")); total2 = drTotal.IsDBNull(drTotal.GetOrdinal("TotalDevengado")) ? 0 : drTotal.GetDecimal(drTotal.GetOrdinal("TotalDevengado")); } sc.Close();
二、实现分组求和行插入逻辑
核心思路:先按noUnidad分组计算每个分组的数值列总和,然后倒序遍历原DataTable的行,当检测到分组变化时插入求和行。
以下是完整的修改后的llenarTabla方法:
private void llenarTabla() { int mes = Convert.ToInt32(meses.SelectedIndex + 1); int year = Convert.ToInt32(ano.Text); string recaudacion = numAdmin.Text; // 1. 参数化查询获取数据 string mostrarTurnos = @"SELECT * FROM TablaSalidas WHERE noAdmin = @noAdmin AND MONTH(fecha) = @mes AND YEAR(fecha) = @year ORDER BY noUnidad ASC, fecha"; SqlDataAdapter da = new SqlDataAdapter(mostrarTurnos, sc); da.SelectCommand.Parameters.AddWithValue("@noAdmin", recaudacion); da.SelectCommand.Parameters.AddWithValue("@mes", mes); da.SelectCommand.Parameters.AddWithValue("@year", year); DataTable dt = new DataTable(); da.Fill(dt); // 2. 按noUnidad分组计算每个分组的总和 var groupTotals = dt.AsEnumerable() .GroupBy(row => row["noUnidad"]) .Select(g => new { Unidad = g.Key, TotalSalida = g.Sum(row => row.IsNull("importeSalida") ? 0 : Convert.ToDecimal(row["importeSalida"])), TotalRuta = g.Sum(row => row.IsNull("importeRuta") ? 0 : Convert.ToDecimal(row["importeRuta"])), TotalDevengado = g.Sum(row => row.IsNull("importeDevengado") ? 0 : Convert.ToDecimal(row["importeDevengado"])) }) .ToList(); // 3. 倒序遍历行,插入分组求和行(避免索引偏移) object currentUnidad = null; for (int i = dt.Rows.Count - 1; i >= 0; i--) { DataRow row = dt.Rows[i]; object unidad = row["noUnidad"]; if (currentUnidad != null && !unidad.Equals(currentUnidad)) { // 查找当前分组的总和 var groupTotal = groupTotals.First(g => g.Unidad.Equals(currentUnidad)); // 创建求和行 DataRow subtotalRow = dt.NewRow(); subtotalRow["noUnidad"] = "Subtotal"; subtotalRow["nombreRuta"] = $"Unidad {currentUnidad}"; subtotalRow["importeSalida"] = groupTotal.TotalSalida; subtotalRow["importeRuta"] = groupTotal.TotalRuta; subtotalRow["importeDevengado"] = groupTotal.TotalDevengado; // 插入到当前行的下一行 dt.Rows.InsertAt(subtotalRow, i + 1); } currentUnidad = unidad; } // 4. 添加整体总计行 decimal total = 0, total1 = 0, total2 = 0; string totalQuery = @"SELECT SUM(importeSalida) as TotalSalida, SUM(importeRuta) as TotalRuta, SUM(importeDevengado) as TotalDevengado FROM TablaSalidas WHERE noAdmin = @noAdmin AND MONTH(fecha) = @mes AND YEAR(fecha) = @year"; SqlCommand cmdTotal = new SqlCommand(totalQuery, sc); cmdTotal.Parameters.AddWithValue("@noAdmin", recaudacion); cmdTotal.Parameters.AddWithValue("@mes", mes); cmdTotal.Parameters.AddWithValue("@year", year); sc.Open(); SqlDataReader drTotal = cmdTotal.ExecuteReader(); if (drTotal.Read()) { total = drTotal.IsDBNull(drTotal.GetOrdinal("TotalSalida")) ? 0 : drTotal.GetDecimal(drTotal.GetOrdinal("TotalSalida")); total1 = drTotal.IsDBNull(drTotal.GetOrdinal("TotalRuta")) ? 0 : drTotal.GetDecimal(drTotal.GetOrdinal("TotalRuta")); total2 = drTotal.IsDBNull(drTotal.GetOrdinal("TotalDevengado")) ? 0 : drTotal.GetDecimal(drTotal.GetOrdinal("TotalDevengado")); } sc.Close(); DataRow totalRow = dt.NewRow(); totalRow["nombreRuta"] = "Total General"; totalRow["importeSalida"] = total; totalRow["importeRuta"] = total1; totalRow["importeDevengado"] = total2; dt.Rows.Add(totalRow); dt.AcceptChanges(); // 5. 表格列设置(保留原有的格式和显示设置) tabla.DataSource = dt; tabla.Columns["noUnidad"].HeaderText = "Unidad"; tabla.Columns["fecha"].HeaderText = "Fecha"; tabla.Columns["noRuta"].HeaderText = "No. Ruta"; tabla.Columns["noTerminal"].HeaderText = "No. Terminal"; tabla.Columns["noRecaudador"].HeaderText = "No. Recaudador"; tabla.Columns["noAdmin"].HeaderText = "No. de Admin"; tabla.Columns["nomAdmin"].HeaderText = "Nombre de Admin"; tabla.Columns["importeSalida"].HeaderText = "Imp. entregado"; tabla.Columns["importeRuta"].HeaderText = "Imp. de ruta"; tabla.Columns["importeDevengado"].HeaderText = "Imp. devengado"; tabla.Columns["nombreRuta"].HeaderText = "Ruta"; // 隐藏不需要的列 tabla.Columns["nomAdmin"].Visible = false; tabla.Columns["noTerminal"].Visible = false; tabla.Columns["noRecaudador"].Visible = false; tabla.Columns["noAdmin"].Visible = false; tabla.Columns["Estado"].Visible = false; // 设置数值列格式 tabla.Columns["importeSalida"].DefaultCellStyle.Format = "c"; tabla.Columns["importeRuta"].DefaultCellStyle.Format = "c"; tabla.Columns["importeDevengado"].DefaultCellStyle.Format = "c"; // 列自动调整和显示顺序 tabla.Columns["Estado"].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; tabla.Columns["noUnidad"].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; tabla.Columns["noRuta"].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; tabla.Columns["fecha"].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; tabla.Columns["nombreRuta"].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; tabla.Columns["importeRuta"].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; tabla.Columns["noUnidad"].DisplayIndex = 0; tabla.Columns["fecha"].DisplayIndex = 1; tabla.Columns["nombreRuta"].DisplayIndex = 2; tabla.Columns["Estado"].DisplayIndex = 4; tabla.Columns["importeSalida"].DisplayIndex = 5; tabla.Columns["importeRuta"].DisplayIndex = 6; tabla.Columns["importeDevengado"].DisplayIndex = 8; tabla.RowHeadersVisible = false; tabla.AllowUserToAddRows = false; }
关键说明
- 倒序遍历:插入行时会改变原DataTable的行索引,倒序遍历可以避免索引偏移导致的分组判断错误。
- LINQ分组求和:使用DataTable的
AsEnumerable()方法配合LINQ分组,简洁高效地计算每个Unidad的总和。 - 空值处理:计算总和时判断字段是否为Null,避免转换异常。
- 参数化查询:彻底解决SQL注入风险,同时提升查询性能。
内容的提问来源于stack exchange,提问作者R. Contreras
相关产品推荐
相关产品推荐

