首页 > 解决方案 > 如何在用户控制面板中搜索项目?

问题描述

当我尝试从我的数据库中搜索用户控件中的数据时,它会搜索或过滤我在搜索文本框中键入的数据。这是我用来尝试搜索或过滤的代码

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 框中搜索的内容。

标签: c#sqlwinformsuser-controls

解决方案


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

在我看来,您有一个Bills带有列的数据库表BillName。操作员在 TextBox 中键入了一些文本txtSearch,并且您想要获取所有 BillName 以 TextBox 中的文本开头的账单。

我在这里看到几个问题

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]等,我的代码仍然可以工作,即使在插入或重新排序表的列之后。

好消息是,您将能够对方法 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 注入。

推荐阅读