繁体   English   中英

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

[英]How to update edits in DataGridView to table

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

我想在移到 DataGridView 中的另一行后立即更新数据库的版本。 但我还没有找到一种方法来做到这一点。 此处介绍的解决方案似乎不适用于 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[]”。

任何提示表示赞赏。

MVVM

在现代编程中,倾向于将 model 从视图中分离出来。 这种分离可以更轻松地更改数据的显示方式,而无需更改 model。 您还可以更改 model 的部件,而无需更改显示。 重用 model 并对其进行单元测试更容易,而无需启动 forms 程序。

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

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

在数据源中使用 BindingList

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

我不知道您将在 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) {...}

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

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

使用 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();

这将显示产品。 但是,操作员所做的更改:添加/删除/编辑行不会更新。 如果您需要此功能,则 Products 需要放入实现IBindingList的 object ,例如(惊喜!) BindingList<Product>

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 进行任何更改:添加/删除行,或更改一行中的 Displayed 值时,这些更改都会反映在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);
}

回到你的问题

问问自己:position 更改后立即更新数据库是否明智?

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

另一种场景:需要交换产品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>();

您可以使用 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;
    }
}

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM