How to implement a more efficient search?
答案部分取决于您对高效的定义。我怀疑你的意思是更少的代码和更少的 if 块等。但从根本上说,运行一个新的SELECT * 查询来应用过滤器是低效的,因为你的基础数据集可以是所有的行,你只是摆弄用户的视图。
我有一个数据库,其中 Fish、Color(字符串)、Bird、Group的列中有随机数据> (int) 和 Active 这对于问题中的 Name、Age 和 Gender 应该足够相似 - 或底部的另一个长的东西。
数据表
填写数据表并将其绑定到 DGV:
' form level object
Private dtSample As DataTable
...
' elsewhere
Dim sql = "SELECT Id, Name, Descr, `Group`, Fish, Bird, Color, Active FROM Sample"
Using dbcon As MySqlConnection = New MySqlConnection(MySQLConnStr)
' create SELECT command with the Query and conn
Dim cmd As New MySqlCommand(sql, dbcon)
...
daSample.Fill(dtSample)
daSample.FillSchema(dtSimple, SchemaType.Source)
End Using
dgv2.DataSource = dtSample
接下来,我们可以过滤用户对该表的视图而不发出新的查询。
过滤器控件
如果某些字段仅限于某些选择,例如Gender,您可以使用ComboBox 而不是TextBox。这是为了帮助用户成功并避免拼写错误(Make 或 Mael 而不是 Male;或者在这里,正确拼写 Baracuda 我的意思是 Barracuda,呃 Barracuda 没错。
出于说明目的,Fish 是用户可以输入任何内容的东西,但 Bird 仅限于一组选择。如果有Bird 表,则可以从它绑定或填充cboBird。但您也可以从主/基表中填充它:
Dim birds = dtSample.AsEnumerable.Where(Function(d) d.IsNull(5) = False).
Select(Function(d) d.Field(Of String)("Bird")).
Distinct.
ToArray()
cboBird.Items.AddRange(birds)
如果“Finch”是合法选择,但数据库中没有,它不会显示在列表中。根据应用程序,这可能是一件好事:
- 如果用户在
Finch 上进行过滤并且没有结果记录,则不需要MessageBox 或StatusBar 消息来解释空结果集。
- 如果列表中没有某些内容,则表示您预先表明没有这些内容。然后就变成了训练为什么已知元素不在列表中的问题。
- 另一方面,您必须在每次使用之前重新填充这些过滤器控件,以防最近添加了新记录。如果控件位于
Dialog 或不同的 TabPage 上,则可以根据需要轻松执行此操作。
- 它并不总是适用,但它可以帮助用户避免拼写错误。
这两种方法是否有价值取决于应用程序。
DBNull / '无'
我不确定您为什么要在每个子句中添加“无”。如果有人想查看所有“John”或所有“Cod”记录,他们似乎也不会对“none”感兴趣。就个人而言,Null/DBNull 似乎是一种更好的处理方式,但添加或不添加任何一种形式都很容易。
使用 DBNull/None 过滤到只是那些似乎更有价值。上面的鸟列表代码过滤掉了DBNull,我也会过滤掉none。然后,在将结果添加到 ComboBox 之前,先添加一个 `None' 项,使其位于顶部。
同样,这取决于应用程序的功能; Or = 'None',在这种情况下可能非常有意义。
过滤器
对 Fish 和 Group 使用 TextBox,对 Bird 和 Color 使用 ComboBox 和CheckBox 表示Active,代码可以这样形成过滤器:
Dim filterTerms As New List(Of String)
Dim filterFmt = "{0} = '{1}' "
' OR:
' Dim filterFmt = "{0} = '{1}' OR {0} Is Null"
' OR:
' Dim filterFmt = "{0} = '{1}' OR {0} = 'none'"
If String.IsNullOrEmpty(tbSearchFish.Text) = False Then
Dim txt = tbSearchFish.Text.Replace("'", "''")
filterTerms.Add(String.Format(filterFmt, "Fish", txt))
End If
If cboBird.SelectedIndex > -1 Then
filterTerms.Add(String.Format(filterFmt, "Bird", cboBird.SelectedItem.ToString))
End If
If String.IsNullOrEmpty(tbGroup.Text) = False Then
Dim n As Int32
If Int32.TryParse(tbGroup.Text, n) Then
filterTerms.Add(String.Format(filterFmt, "[Group]", n))
End If
End If
If cboColor.SelectedIndex > -1 Then
filterTerms.Add(String.Format(filterFmt, "Color", cboColor.SelectedItem.ToString))
End If
If chkActive.Checked Then
' NOTE: I do not have TreatTinyAsBoolean turned on
' for some reason
filterTerms.Add(String.Format(filterFmt, "Active", "1"))
End If
If filterTerms.Count > 0 Then
Dim filter = String.Join(" AND ", filterTerms)
dtSample.DefaultView.RowFilter = filter
Dim rows = dtSample.DefaultView.Count
End If
- 使用适合应用需要执行的任何
filterFmt
- 仅当相关控件具有值时才会将过滤器项添加到列表中(如上所述,这可能包括 'None')。
- 对于
TextBox,它会转义任何嵌入的记号,例如可能在O'Malley 或D'Artgnan 等名称中找到的记号。它将一个刻度替换为两个。
- 由于 Group 是数字,因此测试了有效的
Int32 输入
- 如果
filterTerms 列表中有元素,则创建过滤字符串
- 过滤器应用于
DefaultView.Filter(您也可以使用DataView或BindingSource),以便代码无需查询数据库即可提供过滤功能。
-
Rows 会告诉你当前视图中有多少行。
唯一有点棘手的是布尔值,如 Gender 或 Active,因为它们实际上解析为三个选择:{Any/Either, A, B}。为此,我会使用 ComboBox 并且对于 SelectedIndex 0 也忽略它。我没有为此烦恼,因为Combo 概念已被充分涵盖。结果:
是否更“高效”?
这仍然取决于。
它不会重新查询数据库来获取应用程序已经拥有的行。
没有新的DBConnection、DBCommand 或其他 DBProvider 对象被创建,只是一个列表。
无需在循环中动态创建带有N个参数的SQL语句,以避免SQL注入/特殊单词和字符。
它甚至不查询用于过滤条件的项目的数据库。如果数据库中有静态列表,它们可以加载一次,第一次使用过滤器。
删除过滤器很容易,没有WHERE无需再次查询子句。
ComboBox 在适用的情况下帮助用户找到他们想要的内容并避免拼写错误。
是 SQL 的“清洁剂”。更“高效?代码并没有真正与新 SQL 混淆,只是一些 WHERE 子句。
代码少了吗?我不知道,因为我们只是看到结果。它不会把我当作很多代码来完成它的工作。