DataGridView 更改后如何更新数据库?

Posted

技术标签:

【中文标题】DataGridView 更改后如何更新数据库?【英文标题】:How can I update Database after changes in DataGridView? 【发布时间】:2021-12-11 04:58:49 【问题描述】:

我有两个外键表,第一个是:

tblFile : FileID, FileName...

第二个是:

tblChild(在 Datagridview 中):ChildID、ChildName、ChildBirthDate、fkFileID。

单击保存按钮时(在表单中),如果行(添加或编辑或删除),我如何更新第二个表中的所有行? (例如:https://i.postimg.cc/jq7Ks3CW/1.jpg)

我试过了:

    public void dgvChildUpdate(DataGridView dgv, TextBox txtID)

    string ChID;
    DataTable dtNew = (DataTable)dgv.DataSource;
    DataTable dtDel = dtNew.GetChanges(DataRowState.Deleted);

    if (dtDel != null)
    
        for (int i = 0; i < dtDel.Rows.Count; i++)
        
            SqlCommand cmd = new SqlCommand("DELETE FROM tblFileChild WHERE ChildID = @ChildID", ClsDB.con);
            //cmd.Parameters.AddWithValue("@ChildID", dtDel.Rows[i]["ChildID"].ToString());
            cmd.ExecuteNonQuery();
        
    
    dtDel = null;

    using (SqlCommand cmd = new SqlCommand("SELECT * FROM tblChild WHERE fkFileID = @FileID", ClsDB.con))
    
        SqlDataAdapter da = new SqlDataAdapter(cmd);
        cmd.Parameters.AddWithValue("@FileID", txtID.Text);
        DataTable dt = new DataTable();
        da.Fill(dt);
        if (dt.Rows.Count > 0)
        
            for (int t = 0; t < dt.Rows.Count; t++)
            
                ChID = dt.Rows[t]["ChildID"].ToString();
                for (int i = 0; i < dgv.Rows.Count; i++)
                
                    if (ChID == dgv.Rows[i].Cells[5].Value.ToString())
                    
                        using (SqlCommand cmdChild = new SqlCommand("UPDATE tblChild SET ChildName = @ChildName, ChildBirthDate = @ChildBirthDate WHERE ChildID = @ChildID", ClsDB.con))
                        
                            cmdChild.Parameters.AddWithValue("@ChildID", dgv.Rows[i].Cells[0].Value);
                            cmdChild.Parameters.AddWithValue("@ChildName", dgv.Rows[i].Cells[1].Value ?? DBNull.Value);
                            cmdChild.Parameters.AddWithValue("@ChildBirthDate", dgv.Rows[i].Cells[2].Value ?? DBNull.Value);
                            cmdChild.ExecuteNonQuery();
                        
                    
                
            
        
        foreach (DataGridViewRow rowCh in dgv.Rows)
        
            ChID = rowCh.Cells[5].Value.ToString();
            if (ChID == string.Empty)
            
                using (SqlCommand cmdChild = new SqlCommand("INSERT INTO tblChild (ChildName, ChildBirthDate, fkFileID) VALUES (@ChildName, @ChildBirthDate, (SELECT FileID FROM tblFile WHERE FileID = @FileID))", ClsDB.con))
                
                    cmdChild.Parameters.AddWithValue("@ChildName", rowCh.Cells[1].Value ?? DBNull.Value);
                    cmdChild.Parameters.AddWithValue("@ChildBirthDate", rowCh.Cells[3].Value ?? DBNull.Value);
                    cmdChild.Parameters.AddWithValue("@FileID", txtID.Text.Trim());
                    cmdChild.ExecuteNonQuery();
                
            
        
    

【问题讨论】:

我可以向您展示如何使这变得非常简单,但您可能不得不接受丢弃大部分已编写的内容.. 好的,我会尝试..继续.. 不要使用addwithvalue 我稍后会输入它;得到一个 conf 调用 atm.. SMor 的意思是“不要将 AddWithValue 与 SQLServer 一起使用”,但这不是解决您当前问题的方法,更多的是关于良好 SQLS 查询实践的一般建议 【参考方案1】:

我将几个表放在一个临时数据库中来代表您的设置。我不喜欢用 tbl 等为表添加前缀,但我已经将它留在了演示中,即使它在数据库中,我们也不必在 c# 中使用它(我建议不要在数据库中添加任何前缀) )

这是我的数据库版本:

创建一个新项目,添加一个 DataSet 类型的文件并打开它。右键表面,添加一个tableadapter

配置连接字符串等,选择从语句中添加

放置一个按主键拉取的查询

在查询名称中添加“ByFileId”

完成,并重命名生成的数据表以删除“tbl”

对另一个表重复该过程。你应该也看到关系出现了

这是您的数据访问部分排序。如果您愿意,可以查看 VS 在 DataSetX.Designer.cs 中为您编写的大量参数化的 SQL 代码。它不仅对查询进行了参数化,防止了 SQL 注入黑客攻击,而且还添加了额外的代码来检索由 db 计算的值(如 ID),还添加了一些位来检测其他人是否在您更改记录时更改了记录有它,所以你可以管理潜在的冲突:

在设计器模式下打开一个新表单,并显示数据源窗口(查看菜单、其他窗口 - 希望您使用的是 .net 框架):

数据源中有两个Child节点,一个是顶层,一个与File相关;我们将使用文件一,因为它会演示加载和保存相关数据

将以下节点拖到窗体上:

我不想在这里赢得任何选美比赛;只是演示一个概念。可以稍后再试

就是这样;加载更新和保存数据所需的一切都已完成。你说得对;我没有写一行代码

让我们将一些虚拟数据放入数据库中:

而且,在我们加载应用程序之前,我将调整 tableadapter 上的一个属性,这将使加载大量数据变得更加容易(因为目前数据集中的唯一查询一次只能加载一条记录) .关闭两个 tableAdapters 中的 ClearBeforeFill(在表单下的托盘中)

运行应用程序。我通过在框中输入 1,单击填充,更改为 2,填充,然后在子框 1,填充,2,填充,3,填充中加载了我的数据库中的所有可用数据——记住这只是一个演示;所有这些都可以通过编程方式完成,并且可以使用按名称选择的不同查询等(tableadapter 可以有多个查询)

您可以单击顶部的导航在文件1和2之间切换,并注意网格中的相关记录会自动更新:

你可以编辑你看到的任何东西并点击保存

它在数据库中并完成了..

如果您添加装饰或删除记录,这些更改也会被保存,Update() - 尽管被称为 Update(),但它不只是调用 UPDATE 查询

顺便说一句,这不是魔法;您可以在 .Designer 文件和表单代码隐藏中找到所有代码。只是 VS 可以比我们更好地编写所有代码所以让 VS 去做它,只需使用它为您编写的代码


那么我们如何让这些东西变得有用呢?通过 PK 查询一切都很好,但您不会要求用户键入 PK。您可以向执行其他操作的 tableadapter 添加更多查询:

将这些查询添加到您的适配器后,您可以向用户询问文件名,然后运行如下代码:

fileTableAdapter.FillByName(someDataset.File, filenametextbox.Text);
foreach(var f in someDataset.File)
  childTableAdapter.FillByFileId(someDataset.Child, f.FileId);

这将按名称提取文件(用户可以在文件名文本框中键入 % 作为通配符),然后对于每个文件记录,提取所有相关的子记录。

字符串类型数据集中的表也更适合通过 LINQ 进行查询:

someDataset.File.Where(f => f.FileId == "hello");

【讨论】:

除了DataSet和navigation还有其他方法吗,比如DataBinding或者SqlCommandBuilder来完成我之前的Code in Delete case? 如果您的意思是“有没有办法在不先下载行的情况下删除它们?”然后确定-右键单击相关的表适配器,添加查询..删除行的查询..使用您想要的任何条件编写查询,然后您就可以调用它。例如,您可以添加类似的查询 DELETE FROM Person WHERE Name like @name,叫它DeleteByName,叫它像somePersonTableAdapter.DeleteByName("smith%"); 我不太明白你的目标是什么。数据行有一个属性 (RowState),用于跟踪对该行所做的操作。下载时,行状态不变。如果您更改某些属性,例如将 Name 设置为“hello”,则 RowState 将被修改。如果删除该行,它将标记为已删除。如果您新建一个并将其添加到表中,则会添加行状态。当需要保存表适配器时,将枚举所有更改的行,查看它们的 RowState;如果添加了状态,则运行 INSERT。如果已修改,则运行 UPdATE。如果已删除,则运行 DELETE DataRows 还出于各种目的跟踪原始值和新值,包括了解其他人是否在您获得副本时更改了行(因此您最终不会用您的更改覆盖他们的更改)-您在评论中发布的代码看起来像是在尝试复制已经以强大的方式存在的功能

以上是关于DataGridView 更改后如何更新数据库?的主要内容,如果未能解决你的问题,请参考以下文章

VB.NET 2008 DataGridView 不更新 Visual Foxpro 数据库

对 DataGridView 进行排序后,如何更新数据源以反映 DataGridView 的绑定?

更新后如何连续刷新datagridview

在c#中编辑datagridview后如何更新我的数据库

DataGridView透明行选择和双击背景颜色更改

每次更新 datagridview 时如何创建历史日志