簡體   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