c# - 如何在用户控制面板中搜索项目?
问题描述
当我尝试从我的数据库中搜索用户控件中的数据时,它会搜索或过滤我在搜索文本框中键入的数据。这是我用来尝试搜索或过滤的代码
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
,并且您想要获取所有 BillName 以 TextBox 中的文本开头的账单。
我在这里看到几个问题
SQL 注入
SQL 注入是一种可能会破坏数据库的代码注入技术。SQL 注入是最常见的网络黑客技术之一。SQL 注入是在 SQL 语句中放置恶意代码
如果操作员键入以下文本,请查看您的 Sql 文本:“John%; DROP TABLE Bills;--”
Select * from Bills where (billname) like %John%; DROP TABLE Bills; --%
你会失去所有的账单!
解决方案:永远不要将输入数据添加到您的 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 注入。
推荐阅读
- ios - Objective-C 中的数据序列化(Avro 替代方案)
- java - 在 maven clean install 期间查找不同版本的组件(来自 pom 中定义的)
- python - Python 请求导致 401 未经授权
- javascript - 如何添加添加行按钮 React?
- javascript - 如何在没有 window.scrollBy(x, y) 的情况下上下滚动 HTML DOM(我需要在一个元素中向下滚动)
- sql - 我想通过在 sql 中的现有值之间插入 0(零)来更新表的列
- windows - Saltstack 对状态变化的操作
- java - 将两个 A5 横向 PDF 合并为单个 A4 纵向 PDF
- python-3.x - 在 while 循环中陷入 if 语句。我该如何退出?
- view - Odoo 12:根据条件更改表单视图中的字段颜色(字体颜色)