C#连接Access实现库存计算并绑定DataGridView问题
解决WinForms库存计算与DataGridView绑定无数据问题
我来帮你一步步排查问题,搞定库存统计和DataGridView绑定的需求:
一、先揪出现有代码的核心问题
你的代码和SQL查询存在两个关键问题,导致无数据显示:
1. SQL查询的逻辑缺陷
- INNER JOIN的局限性:你用的
INNER JOIN只会返回采购表和销售表中**同时存在相同Bcode**的记录。如果某个产品只有采购记录没有销售,或者只有销售没有采购,就会被直接过滤掉,这些产品的库存自然统计不到。 - 分组与聚合逻辑错误:Access的SQL规则要求,SELECT子句里的非聚合列必须出现在GROUP BY中。你第一次写的查询里
Purchase.InvoiceDate、Purchase.Pname既不在GROUP BY里,也没用到聚合函数(比如SUM、LAST),理论上应该报错;第二次的查询虽然加了聚合,但GROUP BY列冗余,且依然用INNER JOIN漏掉了单边数据。
2. 数据绑定代码的错误
你在while(dr.Read())循环里遍历DataGridView的现有行,但初始时DataGridView是空的,dataGridView1.Rows.Count为0,循环根本不会执行,自然看不到任何数据。
二、修正方案:正确的SQL查询+绑定逻辑
第一步:编写正确的库存统计SQL
我们需要用LEFT JOIN + UNION模拟Access不支持的FULL JOIN,确保所有有采购/销售记录的产品都能被统计,同时正确计算总采购量减总销售量:
-- 统计有采购记录的产品(含无销售的) SELECT p.Bcode, p.Pname, SUM(p.Pqty) AS TotalPurchase, Nz(SUM(s.Pqty), 0) AS TotalSales, SUM(p.Pqty) - Nz(SUM(s.Pqty), 0) AS RemainingQty, LAST(p.InvoiceDate) AS LatestPurchaseDate, LAST(s.InvoiceDate) AS LatestSalesDate FROM Purchase p LEFT JOIN Sales s ON p.Bcode = s.Bcode GROUP BY p.Bcode, p.Pname UNION -- 补充统计只有销售记录的产品 SELECT s.Bcode, s.Pname, 0 AS TotalPurchase, SUM(s.Pqty) AS TotalSales, 0 - SUM(s.Pqty) AS RemainingQty, NULL AS LatestPurchaseDate, LAST(s.InvoiceDate) AS LatestSalesDate FROM Sales s LEFT JOIN Purchase p ON s.Bcode = p.Bcode WHERE p.Bcode IS NULL GROUP BY s.Bcode, s.Pname
Nz函数用来把销售数量的NULL值转为0,避免计算时出现错误;- UNION部分补充了只有销售没有采购的产品,确保库存统计无遗漏。
第二步:正确的DataGridView绑定代码
推荐用DataTable绑定(更简洁易维护),代码如下:
// 假设你的数据库连接cn已正确初始化 string inventorySql = @" SELECT p.Bcode, p.Pname, SUM(p.Pqty) AS TotalPurchase, Nz(SUM(s.Pqty), 0) AS TotalSales, SUM(p.Pqty) - Nz(SUM(s.Pqty), 0) AS RemainingQty, LAST(p.InvoiceDate) AS LatestPurchaseDate, LAST(s.InvoiceDate) AS LatestSalesDate FROM Purchase p LEFT JOIN Sales s ON p.Bcode = s.Bcode GROUP BY p.Bcode, p.Pname UNION SELECT s.Bcode, s.Pname, 0 AS TotalPurchase, SUM(s.Pqty) AS TotalSales, 0 - SUM(s.Pqty) AS RemainingQty, NULL AS LatestPurchaseDate, LAST(s.InvoiceDate) AS LatestSalesDate FROM Sales s LEFT JOIN Purchase p ON s.Bcode = p.Bcode WHERE p.Bcode IS NULL GROUP BY s.Bcode, s.Pname "; // 使用using确保资源自动释放,避免连接泄漏 using (OleDbCommand cmd = new OleDbCommand(inventorySql, cn)) { using (OleDbDataAdapter adapter = new OleDbDataAdapter(cmd)) { DataTable inventoryTable = new DataTable(); adapter.Fill(inventoryTable); // 绑定到DataGridView dataGridView1.DataSource = inventoryTable; // 自定义列名,让界面更友好 dataGridView1.Columns["Bcode"].HeaderText = "产品编号"; dataGridView1.Columns["Pname"].HeaderText = "产品名称"; dataGridView1.Columns["TotalPurchase"].HeaderText = "总采购量"; dataGridView1.Columns["TotalSales"].HeaderText = "总销售量"; dataGridView1.Columns["RemainingQty"].HeaderText = "库存余量"; dataGridView1.Columns["LatestPurchaseDate"].HeaderText = "最后采购日期"; dataGridView1.Columns["LatestSalesDate"].HeaderText = "最后销售日期"; } }
如果一定要用DataReader手动添加行,代码如下(需确保DataGridView已添加对应列):
cn.Open(); OleDbCommand cmd = new OleDbCommand(inventorySql, cn); OleDbDataReader dr = cmd.ExecuteReader(); // 先清空现有行 dataGridView1.Rows.Clear(); while (dr.Read()) { // 新增一行 int rowIdx = dataGridView1.Rows.Add(); // 赋值单元格,注意处理空值 dataGridView1.Rows[rowIdx].Cells["Bcode"].Value = dr["Bcode"].ToString(); dataGridView1.Rows[rowIdx].Cells["Pname"].Value = dr["Pname"].ToString(); dataGridView1.Rows[rowIdx].Cells["TotalPurchase"].Value = dr["TotalPurchase"].ToString(); dataGridView1.Rows[rowIdx].Cells["TotalSales"].Value = dr["TotalSales"].ToString(); dataGridView1.Rows[rowIdx].Cells["RemainingQty"].Value = dr["RemainingQty"].ToString(); dataGridView1.Rows[rowIdx].Cells["LatestPurchaseDate"].Value = dr["LatestPurchaseDate"] != DBNull.Value ? dr["LatestPurchaseDate"].ToString() : ""; dataGridView1.Rows[rowIdx].Cells["LatestSalesDate"].Value = dr["LatestSalesDate"] != DBNull.Value ? dr["LatestSalesDate"].ToString() : ""; } dr.Close(); cn.Close();
三、额外注意事项
- 数据一致性:确保同一个
Bcode在采购表和销售表中对应的Pname一致,否则统计结果会出现产品名称分裂的情况; - 连接安全性:始终用
using包裹数据库连接、命令等对象,避免资源泄漏; - 空值处理:用
Nz和DBNull.Value判断处理空值,避免程序抛出异常。
按照上面的方案修改后,就能正确统计所有产品的库存并显示在DataGridView里了。
内容的提问来源于stack exchange,提问作者Joey Arnanzo
相关产品推荐
相关产品推荐

