繁体   English   中英

C# 和 SQL Server Express 和 DataGridView 在表插入后更新

[英]C# and SQL Server Express and DataGridView update after table insert

我是 SQL 服务器数据库的新手,我在 Winforms 应用程序中有一个简单的 datagridview。 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 中的数据?

我对其他可以达到预期结果的方法持开放态度。 我不允许用户编辑 datagridview 中的值,因为并非表中的所有列都对用户可见,我需要保持索引顺序正确。

我尝试过的事情:

我试图在 gridview 中添加一个新行,如下所示:

sourceDataGridView.rows.add(); // I get a runtime error cannot add row to bound datagrid.
sourceDataGridView.rows[n].cells[0].value = str; 

我试图重置数据网格上的数据源,但这也不起作用。

谢谢你。

您不能以这种方式刷新网格视图。 您必须清除现有数据网格的行并再次将其与数据源绑定

将数据与显示方式分开

在现代编程中,倾向于将数据(模型)与向操作员显示数据的方式(视图)分开。

这使您可以自由更改显示,而无需更改 model。 例如,如果你想显示更少的列,或者想用不同的颜色显示负数,或者你想在 Excel 表中显示它。

同样,您可以更改 Model 而无需更改视图:如果您想从 CSV 文件或 Json 中获取数据,而不是从数据库中获取数据,或者甚至从 Internet 获取数据:您不想要更改您根据此数据所做的视图。

通常您需要一个适配器来使 model 适合显示器。 这个适配器通常被称为 ViewModel。 这三个项目一起缩写为 MVVM。 考虑阅读一些关于此的背景信息。

将 model 和视图分离后,您将有以下方法:

  • 从存储库中获取数据。 存储库是您的数据库,但它可以是任何东西:CSV? Json、XML、互联网或只是用于单元测试的字典。
  • 将数据写入存储库
  • 显示数据
  • 获取已编辑的数据

也许您需要一种方法来找出哪些已编辑的数据已更改,因此需要在您的存储库中进行更新。

您写道,您是 SQL 服务器数据库的新手。 如果我看你的问题的rest,似乎读写数据库不是你的问题。 因此,我不会 go 深入探讨这一点。 我将首先编写如何使用普通的旧 SQL 和 DbReaders 访问数据库。 如果您已经知道如何执行此操作,则可以跳过本章。

之后,我将解释如何显示获取的数据。

访问数据库

您是使用普通的旧 SQL 获取数据,还是使用实体框架? 因为您将其隐藏在您的存储库中,所以这对外部世界无关紧要。 如果您更改获取和更新数据的方式,它不会改变。

唉,你忘了写你在 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

幸运的是,您将 Model 从您的视图中分离出来,并将这些详细信息隐藏在您的存储库 class 中,因此如果您决定使用其他方法访问数据库,则存储库之外的任何内容都不会更改。

您可能还需要左外连接、GroupBy、Where、Order 等。确切的 SQL 有点超出问题的 scope 。

需要记住的重要一点是,使用操作员或其他外部源可能提供的某些输入值更改 SQL 字符串是非常危险的。 如果您从未听说过这个,请阅读SQL 注入的危险

始终将您的 SQL 设为 const 字符串。 使用变量插入运算符输入。

例如,如果您只想在某个 WareHouse Location 展示产品:

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

这里有几件有趣的事情。

返回 IEnumerable

我返回一个 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 将获取的数据一一放入产品中。

while (dbReader.Read())

只要有未读取的提取数据,就返回 true。

Id = dbReader.GetInt64(0),
ProductCode = dbReader.GetString(1),
...

SQL text 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来定义哪一列应该显示哪一个 Product 属性。 这也可以使用 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;
}

这将根据您在设计器中使用的视图规范显示产品:如果您想要价格的特殊格式,或者可能是低库存的红色背景,model 和视图模型都不会发生任何变化。

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 中自动更新。

例如:如果操作员表明他已经完成了产品的更改,他可以按下 OK 或 Apply Now 按钮:

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

现在剩下的唯一挑战是:如何找出哪些 Displayed Products 被编辑。 由于操作员不会每秒多次按下 OK 按钮,所以我只是从数据库中获取原始数据,并将它们与编辑后的数据进行比较,以决定是否需要更新。

我不会只更新所有内容,因为其他人可能会以您可能决定不更新的方式更改数据。 例如,如果您的产品有一个属性 IsObsolete,那么更改价格可能不是明智之举。

结论

通过将ModelView中分离出来, View 变成了一堆单行方法。 大部分工作在 model 中完成。 这个 model 可以在不使用 WinForms 的情况下进行单元测试。

您可以轻松更改数据的显示方式,而无需更改 model。 如果低 StockCount 需要不同的背景颜色,则 Model 不会改变。 如果您想使用 WPF 而不是 Winforms,或者如果您决定通过 Internet 和 windows 服务访问您的数据,则 model 不会更改。

暂无
暂无

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

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