簡體   English   中英

如何在用戶控制面板中搜索項目?

[英]How To Search Item In user control Panel?

當我嘗試從數據庫中搜索用戶控件中的數據時,它會搜索或過濾我在搜索文本框中鍵入的數據。 這是我用來嘗試搜索或過濾的代碼

SqlConnection cn;
SqlCommand cm;
SqlDataReader dr;

 private Label name;
    
 private Label amount;

 private Label descrip;
    
public Form1()
{
    InitializeComponent();
    cn = new SqlConnection(@"Data Source=(LocalDB)");
}

private void Form1_Load(object sender, EventArgs e)
{
    GetData();
}

private void GetData()
{

    cn.Open();
      cm = new SqlCommand("Select * from Bills where (billname) like '%" + txtSearch.Text + "%'", cn);
        dr = cm.ExecuteReader();
            while (dr.Read())
            {
                long len = dr.GetBytes(0, 0, null, 0, 0);
                byte[] array = new byte[System.Convert.ToInt32(len) + 1];
                dr.GetBytes(0, 0, array, 0, System.Convert.ToInt32(len));

                name = new Label();
                        name.Text = dr["billname"].ToString();

                descrip = new Label();
                descrip.Text = dr["billdescrip"].ToString();

                amount = new Label();
                amount.Text = dr["billamount"].ToString();
            }
            dr.Close();
            cn.Close();
}

private void txtSearch_TextChanged(object sender, EventArgs e)
{
    GetData();
}

當我在 txtSearch.text 框中鍵入內容時,結果返回空並且不顯示我試圖在 txtSearch.text 框中搜索的內容。

"Select * from Bills where (billname) like '%" + txtSearch.Text + "%'"

在我看來,您有一個數據庫表Bills ,其中有一列BillName 操作員在 TextBox txtSearch鍵入一些文本,並且您想要獲取具有以 TextBox 中的文本開頭的 BillName 的所有 Bill。

我在這里看到幾個問題

SQL注入

SQL 注入是一種可能會破壞數據庫的代碼注入技術。 SQL 注入是最常見的網絡黑客技術之一。 SQL注入是在SQL語句中放置惡意代碼

如果操作員鍵入以下文本,看看您的 Sql 文本將是什么:“John%; DROP TABLE Bills;--”

Select * from Bills where (billname) like %John%; DROP TABLE Bills; --%

你會失去所有的賬單!

有關 SQL 注入的更多信息

解決方案:永遠不要將輸入數據添加到您的 sql 字符串中! 始終將其添加為參數!

開始使用 using 語句

數據庫連接是一種稀缺資源:您不應該讓它存活時間超過所需時間。 此外,如果您的 SQL 查詢遇到異常,則連接和數據讀取器不會關閉。

養成一個習慣,當一個對象實現 IDisposable 時,你應該使用 using 語句來使用它。

通過這種方式,您可以放心,無論發生什么,在 using 語句結束時,一切都會被正確刷新、寫入、關閉和處理。

SqlConnection、SqlCommand 和 SqlDataReader 應該是 GetData 的私有成員。 這樣您就可以確定沒有人可以篡改您的連接; 您隱藏了從數據庫中獲取數據的方式(SQL 和 SqLCommand,或實體框架和 LINQ?),從而使將來的更改更容易。 您的代碼的讀者不必檢查這些變量的使用位置,並且沒有人濫用它,從而使您的代碼更容易理解。 當然,這將使將 GetData 重用於其他目的成為可能。

這讓我想到了第三個改進:

將數據與其顯示方式分開

在現代編程中,您越來越多地看到日期(= 模型)和數據顯示方式(= 視圖)之間的分離。

  • 分離可以更好地重用代碼,例如:如果您想在控制台程序、WPF 程序、甚至不同的 Form 中使用您的模型,您可以重用模型類。
  • 分離隱藏了獲取數據的方式和位置:它是數據庫嗎? 它是 CSV 文件還是 XML? 你在使用實體框架嗎
  • 這種隱藏允許將來更改而無需更改所有表單
  • 這種隱藏還可以使您的表單更小更易於理解
  • 在開發表單時,您可以模擬實際數據:只需創建一個為您提供示例數據的虛擬類,而不必擔心數據庫
  • 您可以對模型進行單元測試,而無需表單
  • 這幾乎沒有任何額外的工作。

因此,您將擁有模型類:您的數據以及如何保存、再次獲取; 和查看類:您的表單。 您需要一個適配器類來使模型適應視圖:ViewModel。 這三個一起縮寫為 MVVM。 考慮做一些關於 MVVM 的背景閱讀。

落實三項建議

我們創建了一個類,可以保存賬單(和其他項目:客戶?訂單?產品?等),然后您可以再次檢索它們,即使在您重新啟動程序之后。 類似於倉庫,存儲庫,您可以在其中存儲物品並再次取回它們。

interface IOrderRepository
{
    int AddBill(Bill bill);         // return Id of the Bill
    Bill FindBill(int Id);          // null if not found

    // your GetData:
    IEnumerable<Bill> FetchBillsWithNameLike(string name);

    ... // other methods, about Customers, Orders, etc
}

執行:

class OrderRepository : IOrderRepository
{
    private string ConnectionString {get;} = @"Data Source=(LocalDB)";

    private IDbConnection CreateConnection()
    {
        return new SqlConnection(this.ConnectionString);
    }

FetchBillsWithNameLike 的實現:

    public IEnumerable<Bill> FetchBillsWithNameLike(string name)
    {
        using (IDbConnection dbConnection = this.CreateConnection())
        {
            const string sqlText = "Select Id, BillName, CustomerId, ..."
               + " from Bills where (billname) like %@Name%";

            using (IDbCommand = dbConnection.CreateCommand())
            {
                // fill the command and the parameter:
                dbCommand.CommandText = sqlText;
                dbCommand.AddParameterWithValue("@Name", name);

                // execute the command and enumerate the result
                dbConnection.Open();
                using (IDatareader dbReader = dbCommand.ExecuteReader())
                {
                    while (dbReader.Read())
                    {
                        // There is a Bill to read
                        Bill bill = new Bill
                        {
                            Id = dbReader.ReadInt32(0),
                            Name = dbReader.ReadString(1),
                            CustomerId = dbReader.ReadInt32(2),
                            ...
                         };
                         yield return bill;
                    }
                }
            }
        }
    }
    // implement rest of interface
}

多項改進:

  • 連接字符串是一個屬性。 如果您決定對所有 100 種方法使用不同的連接字符串:只需更改一處。
  • 您隱藏了獲取連接字符串的位置:這里是一個常量,但是如果您決定在以后的版本中從配置文件中讀取它:除了這個方法,沒有人知道
  • 您隱藏您正在使用 SqlConnection,您返回接口。 如果在將來的版本中您決定創建不同形式的 IDbConnection,例如對於不同類型的數據庫,如 SQLite,則沒有人知道您創建的是 SqlLiteConnection 對象而不是 SqlConnection。
  • 同理:隱藏SqlCommand,使用接口IDbCommand。
  • 數據庫連接在需要之前不會打開。 這使得其他人可以使用該數據庫,只要您不使用它。
  • 到處都是using語句:如果發生任何異常,所有對象都會被正確關閉和處理。
  • 我自己不創建 DbCommand,而是要求 DbConnection 為我創建它,所以我不必擔心實際 DbConnection 使用哪種類型的命令:它是 SqlCommand? SQLite命令?
  • 如果需要,我會指定表中的哪些列。 如果將來添加了一些列,而我不需要它們,我將不會獲取比我想要的更多的數據。 同樣:如果列被重新排序,它仍然可以工作。

最重要的變化:使用SQL參數防止惡意SQL注入。

  • SQL 文本中的參數通常通過前綴@識別

  • 使用擴展方法AddParameterWithValue ` 添加參數。 某些數據庫在 DbCommand 中將此作為方法(例如:SQLite)

  • 在讀取獲取的數據時,我不會讀取比呼叫者想要的更多的賬單。 因此,如果他使用以下代碼給我打電話:並非所有賬單都會被讀取:

    IOrderRepository 存儲庫 = ... 字符串名稱 = this.ReadName(); bool billsWithNameAvailable = repository.FetchBillsWithName(name).Any();

在這里,我的來電者只想知道是否有任何帶有該名稱的賬單。 讀者根本不會創建任何賬單。

因為 SQL 文本是Select Id, ...並且我讀了dbReader.GetInt32[0]等,即使在插入或重新排序表的列之后,我的代碼仍然可以工作。

好消息是,您將能夠在不使用 Form 的情況下對方法 FetchBillsWithName 進行單元測試:您可以測試如果根本沒有數據庫,或者沒有 Bills 表或空表,或者表不包含列 BillName。 或者如果輸入文本為空會發生什么。 您可以在不需要表單的情況下對各種錯誤進行單元測試。

表格

class Form1 : ...
{
    private IOrderRepository Repository {get;} = new OrderRepository();

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        GetData();
    }

    private void GetData()
    {
        string name = this.txtSearch.Txt;
        foreach(Bill fetchedBill in this.Repository.FetchBillsWithNameLike(name))
        {
            this.ProcessBill(fetchedBill);
        }
    }

    private void ProcessBill(Bill fetchedBill)
    {
        // do your stuff with the label;
    }
}

因為我將模型與視圖分開,所以視圖更簡單:更容易看到真正發生的事情:您專注於表單,而不是如何以及從何處獲取數據。

在開發過程中,雖然您還沒有數據庫,但您可以創建一個虛擬存儲庫並測試您的表單:

class DummyRepository : IOrderRepository
{
    private Dictionary<int, Bill> Bills {get;} = ... // fill with some sample Bills

    // TODO: implement IOrderRepository, using this.Bills
}

如果稍后您決定不從數據庫獲取數據,例如從 Internet 獲取數據,則您的表單幾乎不必更改。 它仍然可以使用IOrderRepository

結論

  • 通過將模型與視圖分離,模型和視圖都更容易紅色和理解。 更容易重用、更改、維護和單元測試。 兩者都可以獨立開發
  • 程序很小,只有一項任務:這使得我們可以重用程序。 更改僅在一個程序中
  • 通過使用接口,我隱藏了獲取數據的方式和位置:SQL? CSV 文件? 互聯網?
  • 通過使用 using 語句,程序更完整地證明:在異常之后,所有屬性都被關閉和處理
  • 通過使用 SQL 參數,我阻止了惡意使用 SQL 注入。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM