【问题标题】:How To Search Item In user control Panel?如何在用户控制面板中搜索项目?
【发布时间】:2021-12-11 09:02:56
【问题描述】:

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

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

【问题讨论】:

  • form1 加载时是否显示结果?
  • 它显示了数据库中的数据,但是当我进行搜索时,面板消失了。我是 usercontrol,controls.clear(),它应该过滤数据并带回我在搜索栏中输入的内容,但它没有这样做
  • @ForeverUnknownNicholas I'm usercontrol,controls.clear() which is suppose to filter the data 是什么意思?那句话没有任何意义,Controls.Clear() 将从面板中删除控件。另外,你为什么要为namedescripamount 重新创建标签,而你可以只更改.Text?此外,最好不要在每次触发TextChanged 事件时执行查询,而是在开始时获取所有数据,然后过滤TextChanged 事件中的数据以找到匹配的记录然后更新用户界面。
  • 顺便说一句:Little Bobby Tables 警报。
  • 我看过这个链接youtube.com/watch?v=WenNmob2oCo。在链接中他们 flowlayoutpanel.control.clear()。在我的项目中,我是一个流程布局面板,但我还在流程布局面板上添加了一个用户控件 s=并尝试在用户控件上进行搜索

标签: c# sql winforms user-controls


【解决方案1】:
"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; --%

你会失去所有的账单!

More information about SQL Injection

解决方案:永远不要将输入数据添加到您的 sql 字符串中!始终将其作为参数添加!

开始使用 using 语句

数据库连接是一种稀缺资源:您不应该让它保持活动的时间超过需要的时间。此外,如果您的 SQL 查询遇到异常,连接和数据读取器不会关闭。

养成习惯,每当一个对象实现 IDisposable 时,您都应该使用 using 语句来使用它。

这样,您可以放心,无论发生什么,在 using 语句结束时,所有内容都已正确刷新、写入、关闭和处置。

SqlConnection、SqlCommand 和 SqlDataReader 应该是 GetData 的私有成员。这样您就可以确定没有人可以篡改您的连接;您隐藏了从数据库中获取数据的方式(SQL 和 SqlCommand,还是实体框架和 LINQ?),从而使未来的更改更容易。您的代码的读者不必检查这些变量的使用位置,并且没有人滥用它,从而使您的代码更易于理解。当然,这也使得将 GetData 重用于其他目的成为可能。

这让我想到了第三个改进:

将数据与显示方式分开

在现代编程中,您会越来越多地看到日期(= 模型)和数据显示方式(= 视图)之间的区别。

  • 分离可以更好地重用代码,例如:如果您想在控制台程序、WPF 程序甚至不同的表单中使用您的模型,您可以重用模型类。
  • 分离隐藏了您获取数据的方式和位置:它是数据库吗?它是 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 吗? SQLiteCommand?
  • 如果需要,我会指定表中的哪些列。如果将来添加了一些列,而我不需要它们,我将不会获取比我想要的更多的数据。同样:如果列重新排序,它仍然可以工作。

最重要的变化:使用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 注入。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-23
    • 1970-01-01
    • 1970-01-01
    • 2018-06-09
    • 1970-01-01
    相关资源
    最近更新 更多