如何更新DataGridView并实现带外键关联的tblChild表批量增删改操作
实现DataGridView中tblChild表的批量增删改操作
你的现有代码思路方向是对的,但存在几个关键问题:只检查了第一行的ChildID来判断更新/新增逻辑,完全没处理删除操作,SQL语句也有可以简化的地方,而且没考虑DataGridView的空行(IsNewRow)。下面给你梳理正确的实现方案:
核心思路
我们可以通过判断DataGridView行的状态(新增、已修改、已删除),结合ChildID是否存在来分别执行对应的SQL操作:
- 新增行:
ChildID为空(或行是IsNewRow),执行INSERT - 修改行:
ChildID存在且行内容有修改,执行UPDATE - 删除行:用户通过DataGridView删除的行,执行DELETE
优化后的完整代码
private void btnSave_Click(object sender, EventArgs e) { // 先获取当前关联的FileID,确保不为空 if (string.IsNullOrWhiteSpace(txtID.Text)) { MessageBox.Show("请先选择关联的文件!"); return; } int fileID = int.Parse(txtID.Text.Trim()); // 维护要删除的ChildID列表(因为DataGridView删除的行会进入Deleted状态) List<int> deletedChildIDs = new List<int>(); // 遍历DataGridView的所有行,跳过空行 foreach (DataGridViewRow row in dgv.Rows) { if (row.IsNewRow) continue; // 跳过新增的空行 // 获取当前行的ChildID,判断是新增还是修改 object childIDValue = row.Cells[0].Value; bool isNewRow = childIDValue == null || string.IsNullOrWhiteSpace(childIDValue.ToString()); if (row.State == DataGridViewRowState.Deleted) { // 处理删除:如果是已删除的行,记录ChildID if (!isNewRow) { deletedChildIDs.Add(int.Parse(childIDValue.ToString())); } continue; } // 获取行数据 string childName = row.Cells[1].Value?.ToString() ?? string.Empty; DateTime? birthDate = row.Cells[2].Value as DateTime?; // 确保必填字段不为空(根据你的业务需求调整) if (string.IsNullOrWhiteSpace(childName)) { MessageBox.Show($"第{row.Index+1}行的姓名不能为空!"); return; } using (SqlConnection con = new SqlConnection(ClsDB.ConnectionString)) // 建议用连接字符串而非全局连接对象 { con.Open(); if (isNewRow) { // 新增操作 string insertSql = @"INSERT INTO tblChild (ChildName, ChildBirthDate, fkFileID) VALUES (@ChildName, @ChildBirthDate, @FileID)"; using (SqlCommand cmd = new SqlCommand(insertSql, con)) { cmd.Parameters.AddWithValue("@ChildName", childName); cmd.Parameters.AddWithValue("@ChildBirthDate", birthDate ?? DBNull.Value); cmd.Parameters.AddWithValue("@FileID", fileID); cmd.ExecuteNonQuery(); } } else { // 更新操作 int childID = int.Parse(childIDValue.ToString()); string updateSql = @"UPDATE tblChild SET ChildName = @ChildName, ChildBirthDate = @ChildBirthDate, fkFileID = @FileID WHERE ChildID = @ChildID"; using (SqlCommand cmd = new SqlCommand(updateSql, con)) { cmd.Parameters.AddWithValue("@ChildID", childID); cmd.Parameters.AddWithValue("@ChildName", childName); cmd.Parameters.AddWithValue("@ChildBirthDate", birthDate ?? DBNull.Value); cmd.Parameters.AddWithValue("@FileID", fileID); cmd.ExecuteNonQuery(); } } } } // 批量处理删除操作 if (deletedChildIDs.Count > 0) { using (SqlConnection con = new SqlConnection(ClsDB.ConnectionString)) { con.Open(); // 用IN语句批量删除,注意如果ID数量很多需要调整(比如用表值参数) string deleteSql = $"DELETE FROM tblChild WHERE ChildID IN ({string.Join(",", deletedChildIDs)})"; using (SqlCommand cmd = new SqlCommand(deleteSql, con)) { cmd.ExecuteNonQuery(); } } } MessageBox.Show("保存成功!"); // 刷新DataGridView数据(可选,根据你的数据绑定方式调整) LoadChildData(); } // 可选:加载tblChild数据到DataGridView的方法示例 private void LoadChildData() { if (string.IsNullOrWhiteSpace(txtID.Text)) return; int fileID = int.Parse(txtID.Text.Trim()); using (SqlConnection con = new SqlConnection(ClsDB.ConnectionString)) { string sql = "SELECT ChildID, ChildName, ChildBirthDate FROM tblChild WHERE fkFileID = @FileID"; SqlDataAdapter sda = new SqlDataAdapter(sql, con); sda.SelectCommand.Parameters.AddWithValue("@FileID", fileID); DataTable dt = new DataTable(); sda.Fill(dt); dgv.DataSource = dt; } }
关键改进点说明
- 处理删除操作:通过捕获
DataGridViewRowState.Deleted的行,收集要删除的ChildID,最后批量删除,效率更高。 - 简化SQL语句:原代码中
fkFileID = (SELECT FileID FROM tblFile WHERE FileID = @FileID)完全可以直接写成fkFileID = @FileID,因为@FileID就是关联的FileID,没必要子查询。 - 避免全局连接对象:建议使用
using包裹SqlConnection,确保连接自动释放,避免连接泄漏问题。 - 跳过空行:判断
row.IsNewRow跳过DataGridView末尾的空行,避免插入无效数据。 - 参数化查询:全程使用参数化查询,防止SQL注入风险。
额外注意事项
- 确保DataGridView的
AllowUserToDeleteRows属性设置为true,这样用户可以通过右键或Delete键删除行。 - 如果你的
ChildID是自增主键,新增行的时候不需要手动赋值,数据库会自动生成。 - 如果需要处理大量数据,批量删除时建议使用表值参数代替IN语句,避免SQL语句过长的问题。
内容的提问来源于stack exchange,提问作者User_Michel
相关产品推荐
相关产品推荐

