C# 和 SQL Server Express 和 DataGridView 在 table 插入后更新
C# and SQL Server Express and DataGridView update after table insert
我是 SQL 服务器数据库的新手,我在 Winforms 应用程序中有一个简单的数据网格视图。 datagridview 绑定到 SQL 服务器数据库。
我需要在按下按钮时更新 datagridview 和后端数据库。
private void btnAdd_Click(object sender, EventArgs e)
{
var str = txtNewSource.Text;
var data = this.tBL_SourceTableAdapter.GetData();
var length = data.Rows.Count;
this.tBL_SourceTableAdapter.Insert(length + 1, str);
sourceDataGridView.refresh(); // This does not refresh the data in the form!
}
我想用刚添加到数据库中的新数据更新绑定的 datagridview。我可以看到数据已添加到数据库中。如果我用 datagridview 关闭 window 并重新打开它,新添加的值是可见的。
如何在插入到绑定数据库后刷新 datagridview 中的数据table?
我愿意接受能够达到预期结果的其他方法。我不允许用户编辑 datagridview 中的值,因为并非 table 中的所有列都对用户可见,我需要保持索引顺序正确。
我尝试过的事情:
我试图在 gridview 中添加一个新行,如下所示:
sourceDataGridView.rows.add(); // I get a runtime error cannot add row to bound datagrid.
sourceDataGridView.rows[n].cells[0].value = str;
我尝试重置数据网格上的数据源,但这也不起作用。
谢谢。
您不能以这种方式刷新网格视图。您必须清除现有数据网格的行并将其再次与数据源绑定
将数据与其显示方式分开
在现代编程中,有一种趋势是将数据(模型)与数据向操作员显示的方式(视图)分开。
这使您可以自由更改显示而无需更改模型。例如,如果您想要显示较少的列,或者想要以不同的颜色显示负数,或者您想要以 Excel sheet.
的形式显示
同样,您可以在不更改视图的情况下更改模型:如果您想从 CSV 文件或 Json 中获取数据,而不是从数据库中获取数据,甚至可能获取数据来自互联网:你不想改变你根据这些数据做出的看法。
通常您需要适配器来使模型适合显示器。此适配器通常称为 ViewModel。这三项一起缩写为 MVVM。考虑阅读有关此的一些背景信息。
模型和视图分离后,您将有方法:
- 从存储库中获取数据。存储库是您的数据库,但它可以是任何东西:CSV? Json、XML、互联网或单元测试字典。
- 将数据写入存储库
- 显示数据
- 获取编辑后的数据
也许您需要一种方法来找出哪些已编辑数据已更改,因此需要在您的存储库中进行更新。
您写道您是 SQL 服务器数据库的新手。如果我看看你的问题的其余部分,似乎读取和写入数据库不是你的问题。因此,我不会深入探讨这一点。我将首先写下如何使用普通的旧 SQL 和 DbReaders 访问数据库。如果您已经知道如何操作,则可以跳过本章。
之后,我将解释如何显示获取的数据。
正在访问数据库
您是使用普通旧 SQL 获取数据,还是使用 entity framework?因为您将它隐藏在您的存储库中,所以这对外界来说并不重要。如果您更改获取和更新数据的方式,它不会改变。
唉,你忘了写你在 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;}
public int StockCount {get; set;}
}
interface IRepository
{
IEnumerable<Product> FetchProductsToDisplay(...);
void UpdateProducts(IEnumerable<Product> product);
}
class Repository : IRepository
{
// TODO: implement
}
如果您使用普通的旧 SQL,那么获取产品将是这样的:
const string sqlTextFetchProducts = @"SELECT TOP ..."
+ @" Id, ProductCode, Name, ..."
+ @" FROM Products;";
确切的 SQL 文本因您使用的数据库管理系统而异。例如 SQLight
使用 Limit 30
而不是 TOP 30
.
幸运的是,您将模型与视图分开,并将这些详细信息隐藏在存储库中 class,因此如果您决定使用不同的方法访问数据库,存储库之外的任何内容都不会改变。
您可能还需要 Left Outer Join、GroupBy、Where、Order 等。确切的 SQL 有点超出问题的范围。
需要记住的重要一点是,使用运算符或其他外部源可能提供的某些输入的值来更改 SQL 字符串是非常危险的。如果您从未听说过这个,请阅读 Dangers of SQL injection
Always make your SQL a const string. Use variables to insert operator input.
例如,如果您只想在某个仓库位置展示产品:
const string sqlTextFetchProducts = @"SELECT ... FROM Products;";
+ @" WHERE WareHouseLocationId = @WareHouseLocationId"
好的,让我们实现 FetchProductsToDisplay:
private string DbConnectionString => ...; // gets the database connection string
IEnumerable<Product> FetchProductsToDisplay(int wareHouseLocationId);
{
const string sqlTextFetchProducts = @"...;";
using (var dbConnection = new SQLiteConnection(this.DbConnectionString))
{
using (var dbCommand = dbConnection.CreateCommand())
{
dbCommand.CommandText = sqlTextFetchProducts ;
dbCommand.Parameters.AddWithValue("@WareHouseLocationId", wareHouseLocationId);
dbConnection.Open();
// Execute the query and returns products one by one
using (SQLiteDataReader dbReader = dbCommand.ExecuteReader())
{
while (dbReader.Read())
{
Product fetchedProduct = new Product
{
Id = dbReader.GetInt64(0),
ProductCode = dbReader.GetString(1),
...
Price = dbReader.GetDecimal(4),
StockCount = dbReader.GetInt32(5),
};
yield return fetchedProduct;
}
}
}
}
}
这里有几件有趣的事情。
Return IEnumerable
我 return 一个 IEnumerable:如果我的调用者只使用前几项,将所有获取的数据转换为产品是没有用的。
Product firstProduct = this.FetchProducts(...).Take(25).ToList();
为此创建一个特殊的 SQL 可能更有效,但对于这个示例,您可以看到,您不需要将所有获取的数据转换为产品。
使用参数
SQL 文本不变。参数有一个前缀 @
,以区别于文字文本。这只是约定,您可以更改它,但这可以很容易地找到参数。
参数的值一个一个地添加,例如,如果您只想要 WareHouseLocation 10 的产品,其 StockCount 至少为 2,最高价格为 25 欧元,您可以更改 SQL 这样它包含 @WareHouseLocation, @StockCount, @Price
并且您添加:
IEnumerable<Product> FetchProductsToDisplay(
int wareHouseLocationId,
int minimumStockCount,
decimal maximumPrice)
{
using(...)
...
dbCommand.Parameters.AddWithValue("@WareHouseLocationId", wareHouseLocationId);
dbCommand.Parameters.AddWithValue("@StockCount", minimumStockCount);
dbCommand.Parameters.AddWithValue("@Price", maximumPrice);
...
将获取的数据转换为产品
执行查询后,您使用 DbReader 将获取的数据逐一放入 Products。
while (dbReader.Read())
Return只要有未读取的提取数据就为真。
Id = dbReader.GetInt64(0),
ProductCode = dbReader.GetString(1),
...
您的 SQL 文本 Select Id, ProductCode, ... From ...
中提取的项目有一个索引,Id 的索引为 0,ProductCode 的索引为 1,等等。使用正确的 dbReader.Get...
转换提取的项目进入正确的类型。
将 dbReader 中获取的数据转换为您的 class 的确切方法可能因数据库管理系统而异,但我想您会明白要点。
当然,您还需要一种更新产品的方法。这如果相当相似,但是您将使用 `
而不是 ExecuteReader
public void UpdateProductPrice(int productId, decimal price)
{
const string sqlText = "UPDATE " + tableNameProducts
+ " SET Price = @Price"
+ " WHERE Id = @Id;";
using (SQLiteCommand dbCommand = this.DbConnection.CreateCommand())
{
dbCommand.CommandText = sqlText;
dbCommand.Parameters.AddWithValue("@Id", productId);
dbCommand.Parameters.AddWithValue("@Price", productPrice);
dbCommand.ExecuteNonQuery();
}
}
由您来实施 void UpdateProduct(Product product)
。
进入 ViewModel!
展示产品
现在我们有了获取必须显示的产品的方法,我们可以尝试显示获取的产品。虽然您可以通过直接编辑 DataGridViewCells 来使用它,但使用起来更容易 DataGridView.DataSource
:
使用 visual studio 设计器,您添加了 DataGridView 及其列。使用 属性 DataGridView.DataPropertyName 定义哪个列应显示哪个产品 属性。这也可以使用 visual studio 设计器来完成,但您也可以在构造函数中这样做:
public MyForm()
{
InitializeComponent();
this.columnProductId.DataPropertyName = nameof(Product.Id);
this.columnProductName.DataPropertyName = nameof(Product.Name);
...
this.columnProductPrice.DataPropertyName = nameof(Product.Price);
}
此方法的优点是,如果您将来决定更改 Product 属性的标识符,如果您忘记在此处更改它们,编译器会检查它们。当然:visual studio 会自动更改此处的标识符。如果您使用设计器,则不会这样做。
现在要展示的产品是一条线:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>) this.dataGridViewProducts.DataSource,
set => this.dataGridViewProducts.DataSource = value;
}
这将根据您在设计器中使用的视图规范显示产品:如果您想要价格的特殊格式,或者低库存的红色背景,模型中没有任何变化,也没有视图模型。
private IRepository Repository {get;} = new Repository();
private IEnumerable<Product> FetchProductsToDisplay()
{
return this.Repository.FetchProductsToDisplay(...);
}
public void InitProductDisplay()
{
this.DisplayedProducts = new BindingList<Product>(
this.FetchProductsToDisplay().ToList());
}
还有宾果游戏!所有产品都以您在视图中定义的格式显示。操作员所做的所有更改:添加/删除/更改显示的产品都会在 BindingList 中自动更新。
例如:如果操作员指示他已完成更改产品,他可以按“确定”或“立即应用”按钮:
private void OnButtonApplyNow_Clicked(object sender, ...)
{
Collection<Product> editedProducts = this.Displayedproducts();
// find out which Products are changed and save them in the repository
this.ProcessEditedProducts(editedProducts);
}
现在剩下的唯一挑战是:如何找出编辑了哪些展示产品。由于操作员不会每秒按几次确定按钮,我只是从数据库中获取原始数据,并将它们与编辑后的数据进行比较,以决定是否需要更新。
我不会只更新所有内容,因为其他人可能以您可能决定不更新的方式更改了数据。例如,如果您的产品有 属性 IsObsolete,那么更改价格可能并不明智。
结论
通过将 Model
与 View
分开,View 已成为一堆单一的线性方法。大部分工作都是在模型中完成的。此模型可以在不使用 WinForms 的情况下进行单元测试。
您可以轻松更改数据的显示方式,而无需更改模型。如果较低的 StockCount 需要不同的背景颜色,则模型不会更改。
如果您想使用 WPF 而不是 Winforms,或者如果您决定通过 Internet 和 windows 服务访问您的数据,则无需更改模型。
我是 SQL 服务器数据库的新手,我在 Winforms 应用程序中有一个简单的数据网格视图。 datagridview 绑定到 SQL 服务器数据库。
我需要在按下按钮时更新 datagridview 和后端数据库。
private void btnAdd_Click(object sender, EventArgs e)
{
var str = txtNewSource.Text;
var data = this.tBL_SourceTableAdapter.GetData();
var length = data.Rows.Count;
this.tBL_SourceTableAdapter.Insert(length + 1, str);
sourceDataGridView.refresh(); // This does not refresh the data in the form!
}
我想用刚添加到数据库中的新数据更新绑定的 datagridview。我可以看到数据已添加到数据库中。如果我用 datagridview 关闭 window 并重新打开它,新添加的值是可见的。
如何在插入到绑定数据库后刷新 datagridview 中的数据table?
我愿意接受能够达到预期结果的其他方法。我不允许用户编辑 datagridview 中的值,因为并非 table 中的所有列都对用户可见,我需要保持索引顺序正确。
我尝试过的事情:
我试图在 gridview 中添加一个新行,如下所示:
sourceDataGridView.rows.add(); // I get a runtime error cannot add row to bound datagrid.
sourceDataGridView.rows[n].cells[0].value = str;
我尝试重置数据网格上的数据源,但这也不起作用。
谢谢。
您不能以这种方式刷新网格视图。您必须清除现有数据网格的行并将其再次与数据源绑定
将数据与其显示方式分开
在现代编程中,有一种趋势是将数据(模型)与数据向操作员显示的方式(视图)分开。
这使您可以自由更改显示而无需更改模型。例如,如果您想要显示较少的列,或者想要以不同的颜色显示负数,或者您想要以 Excel sheet.
的形式显示同样,您可以在不更改视图的情况下更改模型:如果您想从 CSV 文件或 Json 中获取数据,而不是从数据库中获取数据,甚至可能获取数据来自互联网:你不想改变你根据这些数据做出的看法。
通常您需要适配器来使模型适合显示器。此适配器通常称为 ViewModel。这三项一起缩写为 MVVM。考虑阅读有关此的一些背景信息。
模型和视图分离后,您将有方法:
- 从存储库中获取数据。存储库是您的数据库,但它可以是任何东西:CSV? Json、XML、互联网或单元测试字典。
- 将数据写入存储库
- 显示数据
- 获取编辑后的数据
也许您需要一种方法来找出哪些已编辑数据已更改,因此需要在您的存储库中进行更新。
您写道您是 SQL 服务器数据库的新手。如果我看看你的问题的其余部分,似乎读取和写入数据库不是你的问题。因此,我不会深入探讨这一点。我将首先写下如何使用普通的旧 SQL 和 DbReaders 访问数据库。如果您已经知道如何操作,则可以跳过本章。
之后,我将解释如何显示获取的数据。
正在访问数据库
您是使用普通旧 SQL 获取数据,还是使用 entity framework?因为您将它隐藏在您的存储库中,所以这对外界来说并不重要。如果您更改获取和更新数据的方式,它不会改变。
唉,你忘了写你在 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;}
public int StockCount {get; set;}
}
interface IRepository
{
IEnumerable<Product> FetchProductsToDisplay(...);
void UpdateProducts(IEnumerable<Product> product);
}
class Repository : IRepository
{
// TODO: implement
}
如果您使用普通的旧 SQL,那么获取产品将是这样的:
const string sqlTextFetchProducts = @"SELECT TOP ..."
+ @" Id, ProductCode, Name, ..."
+ @" FROM Products;";
确切的 SQL 文本因您使用的数据库管理系统而异。例如 SQLight
使用 Limit 30
而不是 TOP 30
.
幸运的是,您将模型与视图分开,并将这些详细信息隐藏在存储库中 class,因此如果您决定使用不同的方法访问数据库,存储库之外的任何内容都不会改变。
您可能还需要 Left Outer Join、GroupBy、Where、Order 等。确切的 SQL 有点超出问题的范围。
需要记住的重要一点是,使用运算符或其他外部源可能提供的某些输入的值来更改 SQL 字符串是非常危险的。如果您从未听说过这个,请阅读 Dangers of SQL injection
Always make your SQL a const string. Use variables to insert operator input.
例如,如果您只想在某个仓库位置展示产品:
const string sqlTextFetchProducts = @"SELECT ... FROM Products;";
+ @" WHERE WareHouseLocationId = @WareHouseLocationId"
好的,让我们实现 FetchProductsToDisplay:
private string DbConnectionString => ...; // gets the database connection string
IEnumerable<Product> FetchProductsToDisplay(int wareHouseLocationId);
{
const string sqlTextFetchProducts = @"...;";
using (var dbConnection = new SQLiteConnection(this.DbConnectionString))
{
using (var dbCommand = dbConnection.CreateCommand())
{
dbCommand.CommandText = sqlTextFetchProducts ;
dbCommand.Parameters.AddWithValue("@WareHouseLocationId", wareHouseLocationId);
dbConnection.Open();
// Execute the query and returns products one by one
using (SQLiteDataReader dbReader = dbCommand.ExecuteReader())
{
while (dbReader.Read())
{
Product fetchedProduct = new Product
{
Id = dbReader.GetInt64(0),
ProductCode = dbReader.GetString(1),
...
Price = dbReader.GetDecimal(4),
StockCount = dbReader.GetInt32(5),
};
yield return fetchedProduct;
}
}
}
}
}
这里有几件有趣的事情。
Return IEnumerable
我 return 一个 IEnumerable:如果我的调用者只使用前几项,将所有获取的数据转换为产品是没有用的。
Product firstProduct = this.FetchProducts(...).Take(25).ToList();
为此创建一个特殊的 SQL 可能更有效,但对于这个示例,您可以看到,您不需要将所有获取的数据转换为产品。
使用参数
SQL 文本不变。参数有一个前缀 @
,以区别于文字文本。这只是约定,您可以更改它,但这可以很容易地找到参数。
参数的值一个一个地添加,例如,如果您只想要 WareHouseLocation 10 的产品,其 StockCount 至少为 2,最高价格为 25 欧元,您可以更改 SQL 这样它包含 @WareHouseLocation, @StockCount, @Price
并且您添加:
IEnumerable<Product> FetchProductsToDisplay(
int wareHouseLocationId,
int minimumStockCount,
decimal maximumPrice)
{
using(...)
...
dbCommand.Parameters.AddWithValue("@WareHouseLocationId", wareHouseLocationId);
dbCommand.Parameters.AddWithValue("@StockCount", minimumStockCount);
dbCommand.Parameters.AddWithValue("@Price", maximumPrice);
...
将获取的数据转换为产品 执行查询后,您使用 DbReader 将获取的数据逐一放入 Products。
while (dbReader.Read())
Return只要有未读取的提取数据就为真。
Id = dbReader.GetInt64(0),
ProductCode = dbReader.GetString(1),
...
您的 SQL 文本 Select Id, ProductCode, ... From ...
中提取的项目有一个索引,Id 的索引为 0,ProductCode 的索引为 1,等等。使用正确的 dbReader.Get...
转换提取的项目进入正确的类型。
将 dbReader 中获取的数据转换为您的 class 的确切方法可能因数据库管理系统而异,但我想您会明白要点。
当然,您还需要一种更新产品的方法。这如果相当相似,但是您将使用 `
而不是ExecuteReader
public void UpdateProductPrice(int productId, decimal price)
{
const string sqlText = "UPDATE " + tableNameProducts
+ " SET Price = @Price"
+ " WHERE Id = @Id;";
using (SQLiteCommand dbCommand = this.DbConnection.CreateCommand())
{
dbCommand.CommandText = sqlText;
dbCommand.Parameters.AddWithValue("@Id", productId);
dbCommand.Parameters.AddWithValue("@Price", productPrice);
dbCommand.ExecuteNonQuery();
}
}
由您来实施 void UpdateProduct(Product product)
。
进入 ViewModel!
展示产品
现在我们有了获取必须显示的产品的方法,我们可以尝试显示获取的产品。虽然您可以通过直接编辑 DataGridViewCells 来使用它,但使用起来更容易 DataGridView.DataSource
:
使用 visual studio 设计器,您添加了 DataGridView 及其列。使用 属性 DataGridView.DataPropertyName 定义哪个列应显示哪个产品 属性。这也可以使用 visual studio 设计器来完成,但您也可以在构造函数中这样做:
public MyForm()
{
InitializeComponent();
this.columnProductId.DataPropertyName = nameof(Product.Id);
this.columnProductName.DataPropertyName = nameof(Product.Name);
...
this.columnProductPrice.DataPropertyName = nameof(Product.Price);
}
此方法的优点是,如果您将来决定更改 Product 属性的标识符,如果您忘记在此处更改它们,编译器会检查它们。当然:visual studio 会自动更改此处的标识符。如果您使用设计器,则不会这样做。
现在要展示的产品是一条线:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>) this.dataGridViewProducts.DataSource,
set => this.dataGridViewProducts.DataSource = value;
}
这将根据您在设计器中使用的视图规范显示产品:如果您想要价格的特殊格式,或者低库存的红色背景,模型中没有任何变化,也没有视图模型。
private IRepository Repository {get;} = new Repository();
private IEnumerable<Product> FetchProductsToDisplay()
{
return this.Repository.FetchProductsToDisplay(...);
}
public void InitProductDisplay()
{
this.DisplayedProducts = new BindingList<Product>(
this.FetchProductsToDisplay().ToList());
}
还有宾果游戏!所有产品都以您在视图中定义的格式显示。操作员所做的所有更改:添加/删除/更改显示的产品都会在 BindingList 中自动更新。
例如:如果操作员指示他已完成更改产品,他可以按“确定”或“立即应用”按钮:
private void OnButtonApplyNow_Clicked(object sender, ...)
{
Collection<Product> editedProducts = this.Displayedproducts();
// find out which Products are changed and save them in the repository
this.ProcessEditedProducts(editedProducts);
}
现在剩下的唯一挑战是:如何找出编辑了哪些展示产品。由于操作员不会每秒按几次确定按钮,我只是从数据库中获取原始数据,并将它们与编辑后的数据进行比较,以决定是否需要更新。
我不会只更新所有内容,因为其他人可能以您可能决定不更新的方式更改了数据。例如,如果您的产品有 属性 IsObsolete,那么更改价格可能并不明智。
结论
通过将 Model
与 View
分开,View 已成为一堆单一的线性方法。大部分工作都是在模型中完成的。此模型可以在不使用 WinForms 的情况下进行单元测试。
您可以轻松更改数据的显示方式,而无需更改模型。如果较低的 StockCount 需要不同的背景颜色,则模型不会更改。 如果您想使用 WPF 而不是 Winforms,或者如果您决定通过 Internet 和 windows 服务访问您的数据,则无需更改模型。