You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 06:05:21