如何将 DataGridView 中的编辑更新为表

Posted

技术标签:

【中文标题】如何将 DataGridView 中的编辑更新为表【英文标题】:How to update edits in DataGridView to table 【发布时间】:2021-08-06 05:48:35 【问题描述】:

我已将 DataGridView 绑定到 .Net 5.0 WinForms 项目中的 SQL Server 表。显示数据效果很好。

我想在移到 DataGridView 中的另一行后立即更新数据库的版本。但我还没有找到一种方法来做到这一点。 here 提出的解决方案似乎不适用于 OleDbDataAdapter。 Update 方法不接受 DataRow。 DOCS 中的示例需要我尽量避免的 DataSet。其他示例使用按钮来保存更改。

数据是这样加载的:

var dataAdapter = new OleDbDataAdapter(sqlQueryString, connString);
var dataTable = new DataTable();
dataAdapter.Fill(dataTable);                // fill data table from SQL server
var bindingSource = new BindingSource();
bindingSource.PositionChanged += new System.EventHandler(bindingSource_PositionChanged);
bindingSource.DataSource = dataTable;       // connect binding source to data table
dataGridView.DataSource = bindingSource;    // connect DataGridView to binding source

对于更新,我终于尝试了这个:

private void bindingSource_PositionChanged(object sender, EventArgs e)

    DataRow dataRow = ((DataRowView)((BindingSource)sender).Current).Row;
    if (dataRow.RowState == DataRowState.Modified)  // this is successful
    
        dataAdapter.Update(dataRow);    // compile error
    

我得到编译错误

无法从“System.Data.DataRow”转换为“System.Data.DataRow[]”。

感谢任何提示。

【问题讨论】:

您的数据库提供者对象非常强大。如果您在表单级别声明它们以便它们持久存在,您可以配置它们以便您只需 Update() DataAdapter 并应用所有待处理的更改。也无需一次跟踪移动和更新 1 行 - 数据表可以存储许多不同的更改。 DataAdapter.Update 不能有 DataRow 类型的参数。您需要传递一个 DataRow 数组,因此只需更改为 dataAdapter.Update(new [] dataRow); 附带说明,请考虑使用SqlClient 托管驱动程序而不是OleDb @Ňɏssa 实际上,我已经在包含 DataGridView 的 UserControl 中声明了它们。我刚刚将这些线路拼凑在一起,因此我不需要发布整个解决方案。我不想将其移至表单级别。 UserControl 应该是自治的。 @Steve 这可能是方向 - 谢谢。我已将该行作为唯一元素分配给一个数组,并将该数组传递给 Update。它运行没有错误,但不更新数据库。当我移回已编辑的行时,我收到错误消息:“在传递带有修改行的 DataRow 集合时,更新需要有效的 UpdateCommand。”有什么建议吗? 【参考方案1】:

MVVM

在现代编程中,存在将模型与视图分离的趋势。这种分离可以更轻松地更改数据的显示方式,而无需更改模型。您还可以更改模型的某些部分而无需更改显示。无需启动表单程序即可更轻松地重用模型并对其进行单元测试。

在 WPF 中,模型和视图之间的这种分离几乎是强制执行的。使用 winform 时,您必须注意不要将它们混合得过多。

为了将这两者分开,需要使用适配器代码将模型粘合到视图上。此适配器代码通常称为视图模型。这三个的缩写通常称为 MVVM。考虑熟悉一下 MVVM 的思想。

在数据源中使用 BindingList

如果要将模型与视图分离,则需要方法来获取必须从数据库中显示的数据,以及更新项目的数据。

我不知道您将在 DataGridView 中显示什么,但我们假设它是一系列产品,如下所示:

class Product

    public int Id get; set;
    public string ProductCode get; set;
    public string Name get; set;
    public string Description get; set;
    public decimal Price get; set;
    ...

您将有方法来获取必须显示的产品,并更新一个产品,或者一次更新多个产品:

IEnumerable<Product> FetchProductsToDisplay(...)

    // TODO: fetch the products from the database.


void UpdateProduct(Product product) ...
void UpdateProducts(IEnumerable<Product> products) ...

实施超出了此问题的范围。顺便说一句,您是否注意到,因为我将获取和更新数据放在单独的过程中,所以我隐藏了产品的保存位置?它可以在 SQL 服务器中,但如果您愿意,它也可以是 CSV 或 XML 文件,甚至是字典,这对于单元测试可能很方便。

此外:您可以在不使用表单的情况下对这些方法进行单元测试。

使用 Visual Studio 设计器,您已经添加了列并定义了哪个列应该显示哪个 Product 属性。你也可以在构造函数中使用属性DataGridViewColumn.DataPropertyName

public MyForm()

    InitializeComponents();

    this.columnProductCode.DataPropertyName = nameof(Product.ProductCode);
    this.columnName.DataPropertyName = nameof(Product.Name);
    ...

您无需为无论如何都不会显示的属性设置 DataPropertyName。

现在要显示产品,将产品分配给 DataGridView 的 DataSource 就足够了:

var productsToDisplay = this.FetchProductsToDisplay(...);
this.dataGridView1.DataSource = productsToDisplay.ToList();

这将显示产品。但是,操作员所做的更改:添加/删除/编辑行不会更新。如果你需要这个功能,那么产品需要放入一个实现IBindingList的对象,比如(惊喜!)BindingList&lt;Product&gt;

private BindingList<Product> DisplayedProducts

    get => (BindingList<Product>)this.dataGridView1.DataSource;
    set => this.dataGridView1.DataSource = value;

初始化 DataGridView:

private void DisplayProducts()

    this.DisplayedProducts = new BindingList<Product>(this.FetchProductsToDisplay().ToList());

现在,只要操作员对 DataGridView 进行任何更改:添加/删除行,或更改连续显示的值,这些更改都会反映在 DisplayedProducts 中。

例如,如果操作员单击Apply Now 表示他已完成产品编辑:

private void OnButtonApplyNow_Clicked(object sender, ...)

    var products = this.DisplayedProducts;
    // find out which Products are Added / Removed / Changed
    this.ProcessEditedProducts(products);

当然,您可以通过编程方式添加/删除/更改显示的产品:

void AddProductsToDisplay()

    Product product = this.DisplayedProducts.AddNew();
    this.FillNewProduct(product);

回到你的问题

扪心自问:一旦位置发生变化就更新数据库是否明智?

如果操作员开始输入,然后记得他可以复制粘贴项目,他将停止输入,转到其他控件进行复制,然后通过粘贴继续编辑单元格。也许他会去其他行查看信息以决定在单元格中放入什么。

另一种场景:产品A和产品B的描述需要交换。考虑为此所需的操作员操作。什么时候更新数据库是明智的?您何时确定运营商对新数据感到满意?

因此,一旦编辑了一行就更新数据库是不明智的。操作员应明确表明他已完成编辑。

private void OnButtonOk_Clicked(object sender, ...)

    var products = this.DisplayedProducts;
    // find out which Products are Added / Removed / Changed
    this.ProcessEditedProducts(products);

进一步改进

一旦您将数据(模型)与该数据的显示方式(视图)分开,使用 DataSource,就可以很容易地访问显示在当前行或选定行中的产品:

Product CurrentProduct => (Product) this.dataGridView1.CurrentRow?.DataBoundItem;

IEnumerable<Product> SelectedProducts = this.dataGridView1.SelectedRows
    .Cast<DataGridViewRow>()
    .Select(row => row.DataBoundItem)
    .Cast<Product>();

【讨论】:

非常感谢哈拉德。你已经解释得很仔细了。实际上,我的网格没有分配给某些数据(如产品)。它应该可用于各种数据。配置在启动时从 SQLite 数据库加载。我会尝试你的建议,这需要时间。逐行保存数据的想法来自 MS Access。我只是习惯了。我会考虑你的方法。 我已经尝试实现您的概念,但到目前为止没有成功。我的第一个问题是在编译时不知道列数、列名和数据类型,因为配置是在运行时加载的。为了尝试它,我用示例数据定义了一个类“产品”。但是我还没有找到如何将数据库数据加载到BindingList。你写了“实施超出了这个问题的范围。”但我的问题正是实施。作为初学者,我需要食谱。抱歉,如果这不重视您的解决方案。 如果您的问题不是关于 DataGridView 以及如何在 DataGridView 中显示项目,而是更多关于如何从数据库中获取项目以及如何更新数据库的问题,那么您为什么要提到 DataGridView?在我看来,您的问题更多与数据库相关,现在与 Winforms 相关。换句话说:你从数据库中获取数据的单元测试还没有工作。 好吧,当我写这个问题时,我不知道数据库和 DataGridView 之间的众多齿轮中的哪一个没有转动。我想我必须阅读更多的理论,然后再试一次。无论如何,感谢您抽出宝贵的时间。【参考方案2】:

你可以使用 foreach 循环。

private void AddInfo()

    // flag so we know if there was one dupe
    bool updated = false;

    // go through every row
    foreach (DataGridViewRow row in dgv_Purchase.Rows)
    
        // check if there already is a row with the same id
        if (row.Cells["Pro_ID"].ToString() == txt_ProID.Text)
        
            // update your row
            row.Cells["Purchase_Qty"] = txt_Qty.Text;

            updated = true;
            break; // no need to go any further
        
    

    // if not found, so it's a new one
    if (!updated)
    
        int index = dgv_Purchase.Rows.Add();

        dgv_Purchase.Rows[index].Cells["Purchase_Qty"].Value = txt_Qty.Text;
    

【讨论】:

感谢您的回答。我无法跟随你的想法。 txt_ProIDtxt_Qty 是什么?它们是文本框吗?我没有任何文本框。只是 DataGridView 中的编辑数据。 感谢您的回复。是的。它们是文本框。 那就没用了。所以我仍然需要一个解决方案,将编辑后的数据从 DataGridView 更新到数据库中。 您可以在 C# windows 窗体项目中使用数据集。 我已将 DataTable 替换为 DataSet[0]。这适用于显示数据。但是更新会导致同样的错误。你能给我一行更新代码吗?【参考方案3】:

最后我找到了缺少的 2 行:

private SqlCommandBuilder commandBuilder;   // on UserControl level
commandBuilder = new SqlCommandBuilder(dataAdapter);    // when loading data

一本书帮助了我:Michael Schmalz,C# 数据库基础,O'Reilly

奇怪的是,SqlDataAdapter 的 DOCS 引用没有提到 SqlCommandBuilder。

感谢所有为新贡献者花费宝贵时间的人。

【讨论】:

以上是关于如何将 DataGridView 中的编辑更新为表的主要内容,如果未能解决你的问题,请参考以下文章

如何在datagridview中插入、更新、删除?

如何在按钮单击时更新datagridview中的记录?

C#如何保留datagridview中的原来数据并将新的数据添加到datagridview中?

如何直接在 Windows 窗体应用程序上编辑/添加/更新 datagridview

在更新到数据库之前编辑 DataTable 中的行

如何使用 C# 将数据表中的值更新到 datagridview?