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



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

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

数据加载方式如下:

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
}
}

我得到编译错误

无法从"系统"转换。数据DataRow"到"系统。数据DataRow[]'。

欢迎任何提示。

MVVM

在现代编程中,存在着将模型与视图分离的趋势。这种分离可以更容易地更改数据的显示方式,而无需更改模型。您也可以在不必更改显示的情况下更改模型的各个部分。重用模型和对其进行单元测试更容易,而无需启动表单程序。

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

为了将这两者分开,需要适配器代码将模型粘贴到视图中。这个适配器代码通常被称为视图模型。这三者的缩写通常被称为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文件,甚至是字典,这对于单元测试来说可能很方便。

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

使用visualstudio设计器,您已经添加了列,并定义了哪个列应该显示哪个Product属性。您也可以使用属性DataGridViewColumn在构造函数中完成此操作。数据属性名称

public MyForm()
{
InitializeComponents();
this.columnProductCode.DataPropertyName = nameof(Product.ProductCode);
this.columnName.DataPropertyName = nameof(Product.Name);
...
}

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

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

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

这将显示产品。但是,操作员所做的更改:添加/删除/编辑行不会更新。如果您需要此功能,那么Products需要放入一个实现IBindingList的对象,如(惊奇!)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:Add/Remove行进行任何更改,或更改行中的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);
}

回到你的问题

问问自己:一旦职位发生变化,立即更新数据库明智吗?

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

另一种情况是:需要交换产品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;
}
}

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

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

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

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

感谢每一位为新贡献者花费宝贵时间的人。

最新更新