ASP.NET 中的排名搜索实现示例






4.59/5 (10投票s)
描述如何使用全文搜索和 SQL Server 2012 开始构建自己的搜索引擎。
引言
当你第一次尝试为网页实现搜索功能时,你会发现很难让它如你所愿地工作。显而易见的是,你已经习惯了 Google 的搜索算法,它们“总是”能返回你想要的结果。然而,通过使用已有的工具和查询,你可以自己实现接近的效果。在本文中,我们将解释如何使用 SQL Server 2012 和 C# 和 ASP.NET 的免费版本来实现这一点。但是,只要你停留在 .NET 平台,本文就可以轻松地转换为任何其他语言。
背景
要了解更多关于全文搜索是什么、如何使用以及底层技术,我提供了一些其他文章的链接。
MSDN 最新指南
http://msdn.microsoft.com/en-us/library/ms142571.aspx
CodeProject 上由 GanesanSenthilvel 撰写的一篇优秀的关于全文搜索编程的文章,解释了很多东西。
https://codeproject.org.cn/Articles/315101/SQL-Full-Text-Search-Programming
所需软件
SQL Server 2012 及高级工具(全文引擎)下载 ENU\x64 SQLEXPRADV_x64_ENU.exe
http://www.microsoft.com/en-us/download/details.aspx?id=29062&WT.mc_id=aff-n-in-loc--pd
请务必通过勾选该复选框来安装全文模块。同时安装 Management Studio,因为它将用于配置。
此视频将带您完成 SQL Express 的首次简单安装。
http://www.youtube.com/watch?v=GeuJEID9rSA
创建表并启用全文搜索
打开 SQL Server Management Studio 并连接到您安装的数据库。创建一个新的数据库实例。
命名数据库并单击“确定”。
在新数据库中创建一个新表。
定义一些列。
并将其中一列设为主键列。
关闭并保存表的设计,将表命名为“Names”。
您也可以运行此脚本来生成此示例中的表。
CREATE TABLE [dbo].[Names](
[MyKey] [nvarchar](50) NOT NULL,
[Name] [nvarchar](50) NULL,
[Email] [nvarchar](50) NULL,
[Description] [text] NULL,
CONSTRAINT [PK_Names] PRIMARY KEY CLUSTERED
(
[MyKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
现在我们准备插入一些测试行。您可以右键单击表并选择“编辑”来轻松更改表,从而输入行。或者,您可以使用脚本生成行。
该脚本
INSERT [dbo].[Names] ([MyKey], [Name], [Email], [Description]) VALUES (N'1', N'Jon', <a href="mailto:N'jon@mail.se'">N'jon@mail.se', N'This is a user')
GO
INSERT [dbo].[Names] ([MyKey], [Name], [Email], [Description]) VALUES (N'2', N'Kim', <a href="mailto:N'kim@mail.se'">N'kim@mail.se', N'My game is yellow.')
GO
INSERT [dbo].[Names] ([MyKey], [Name], [Email], [Description]) VALUES (N'3', N'Hans', <a href="mailto:N'hans@email.se'">N'hans@email.se'</a>, N'Game on')
GO
INSERT [dbo].[Names] ([MyKey], [Name], [Email], [Description]) VALUES (N'4', N'Tom', <a href="mailto:N'clou@post.com'">N'clou@post.com'</a>, N'Soccer')
GO
INSERT [dbo].[Names] ([MyKey], [Name], [Email], [Description]) VALUES (N'5', N'Sven', <a href="mailto:N's@post.com'">N's@post.com'</a>, N'Handball is the best sport')
GO
INSERT [dbo].[Names] ([MyKey], [Name], [Email], [Description]) VALUES (N'6', N'Jonas', <a href="mailto:N'jonas@mails.se'">N'jonas@mails.se'</a>, N'Users')
GO
在 Management Studio 中编辑行
在创建了一些测试行之后,就可以启用全文搜索了。要使其正常工作,需要两件事:一个全局全文目录和一个用于全文的表定义。Express 版本当前存在一个错误,不允许通过向导完全启用此功能。需要通过脚本创建全文目录。创建目录的脚本很简单
CREATE FULLTEXT CATALOG MyCatalog;
GO
成功运行该脚本后,您可以定义您的全文搜索表。右键单击表,选择“全文索引”,然后定义全文索引。
在打开的向导中,单击“下一步”,直到出现表的列。标记您想要搜索的列,然后单击“下一步”直到结束。在此过程中可能会出现一个例外。但是,您可以忽略它并继续。希望您最终成功配置了全文索引。
您的全文索引已准备就绪并已配置。
测试全文表
已创建并配置了具有六行的测试表,可进行一些测试搜索。
SQL Server 中有四种内置函数可用于执行全文搜索查询:Contains、ContainsTable、FreeText 和 FreeTextTable。Contains 和 FreeText 返回一个布尔值,用于 WHERE 子句;ContainsTable 和 FreeTextTable 返回一个包含 KEY 和 RANK(该行的搜索命中点和搜索词)的表。
Contains
在 SQL Management Studio 中打开一个新的查询窗口并运行以下脚本。
SELECT *
FROM Search.dbo.Names
WHERE Contains(*,'mail')
这将返回 Names 表中在任何列(在全文索引中)包含单词“mail”的所有行。Contains 不区分大小写,这意味着它对“Mail”和“mail”的结果相同。
对于前面创建的测试表,此查询返回两行。Contains 只搜索与搜索词完全匹配的单词,但点和“@”也算作单词分隔符。有关详细信息,请参阅 http://msdn.microsoft.com/en-us/library/ms187787
Contains 函数支持布尔运算符(AND、OR、NOT)和特殊字符 *。这意味着像“jon*”这样的搜索将搜索“jonte”、“jonas”以及“jon”。
搜索字符串定义在两个单引号之间。以星号结尾的单词应将字符串字符放在单词周围,例如“Word*”。
搜索字符串示例:““First words” OR Second AND "Third*””
仅在单个列中搜索
SELECT *
FROM Search.dbo.Names
Where Contains(Name,'Jon')
两个特定列
SELECT *
FROM Search.dbo.Names
Where Contains((Name, Email),'Jon')
go
ContainsTable
ContainsTable 的工作方式不同。它返回一个表,其中包含被搜索表行的键以及一个排名命中的搜索分数。这使我们能够构建自己的高性能排名搜索,类似于 Google。或者更像是 BING。
SELECT * FROM ContainsTable(dbo.Names,*,'mail' )
得到
如果您了解 SQL,您已经知道如何结合表来创建排名搜索查询。
SELECT *
FROM CONTAINSTABLE(dbo.Names, *, '"han*"') AS r INNER JOIN
Names ON r.[KEY] = Names.MyKey
order by RANK DESC
这将返回两行,其中 Hans 排在最相关搜索结果的顶部。
FreeText 和 FreeTextTable
Freetext 使用每列指定的语言(在创建全文索引时定义,可以更改)来查找与实际搜索词相似的词。这意味着在英语中,单词“fox”会找到包含“foxes”和“fox”的结果,这对于搜索引擎来说可能非常有用。
与 contains 的一个主要区别是,不使用布尔运算符,它们将被从搜索查询中删除。
FROM FREETEXTTABLE(dbo.Names, *, '"gameing"') AS r INNER JOIN Names ON r.[KEY] = Names.MyKey order by RANK DESC
这让我们为 FreeTextTable 返回了两行,尽管使用了相同的搜索字符串,“gameing”。在 `CONTAIN` 函数中使用“gameing”将导致零行结果。FreeTextTable 函数知道“game”是“gaming”的派生词。FreeText 的工作方式与 contains 类似。
在 Asp.NET 页面中的实现
现在是时候在小型网页中实现了。此示例使用 Visual Studio 2012,但它应该在任何其他支持 Asp.NET 的 IDE 中以类似的方式工作。该页面将只有一个文本框、一个搜索按钮和一个用于显示结果的表。我首先创建一个空的 Asp.NET 项目,并添加一个带有后台代码的 Web Forms 页面。
为了保护数据库免受 SQL 注入的攻击,有些字符是不允许的。这些字符将在稍后在服务器端删除。但是,为了帮助用户不插入这些字符,我们在文本框中添加了一个 AJAX 扩展器。要使用 AJAX,请使用 NuGet 插件将 AJAX 框架添加到您的解决方案中,http://stephenwalther.com/archive/2011/05/23/install-the-ajax-control-toolkit-from-nuget.aspx。扩展器会给出允许的字符,其他字符将无法在文本框中输入。
search.aspx 的代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="search.aspx.cs" Inherits="WebTest.search" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="asp" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Search Site</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="SearchBox" runat="server"></asp:TextBox>
<asp:FilteredTextBoxExtender ID="SearchBox_FilteredTextBoxExtender"
runat="server" Enabled="True" TargetControlID="SearchBox"
ValidChars="*&| ().@åäöÅÄÖ"
FilterType="Custom, Numbers, LowercaseLetters, UppercaseLetters">
</asp:FilteredTextBoxExtender>
<asp:Button ID="SearchButton" runat="server" Text="Search"
OnClick="SearchButton_Click" />
<asp:Table ID="ResultTable" runat="server">
</asp:Table >
<asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</asp:ToolkitScriptManager>
</div>
</form>
</body>
</html>
后台代码将有一个事件函数“SearchButton_Click
”,其中将数据库调用添加到表中。事件代码使用一个名为“SearchDB”的搜索方法,该方法清理搜索字符串以使其正常工作。更新清理功能以支持更高级的搜索。
protected void SearchButton_Click(object sender, EventArgs e)
{
DataTable dt = SearchDB(SearchBox.Text);
if (dt == null)
return;
var row = new TableRow();
//Add the column headers
for (int j = 0; j < dt.Columns.Count; j++)
{
var headerCell = new TableHeaderCell();
headerCell.Text = dt.Columns[j].ColumnName;
row.Cells.Add(headerCell);
}
ResultTable.Rows.Add(row);
//Add the row values
for (int i = 0; i < dt.Rows.Count; i++)
{
row = new TableRow();
for (int j = 0; j < dt.Columns.Count; j++)
{
var cell = new TableCell();
cell.Text = dt.Rows[i][j].ToString();
row.Cells.Add(cell);
}
// Add the TableRow to the Table
ResultTable.Rows.Add(row);
}
}
以及搜索函数。请注意 Regex 函数会清除任何剩余的无效字符。有关如何保护您的网站免受 SQL 注入的信息,请阅读:http://msdn.microsoft.com/en-us/library/ff648339.aspx。
/// <summary>
/// Searches the database in all columns and returns the result as a datatable.
/// </summary>
/// <param name="searchString">Search String</param>
/// <returns>DataTable with the results all columns ordered in rank</returns>
public static DataTable SearchDB(string searchString)
{
if (searchString == null)
return null;
//Clean Up search string to avoid SQL Injection
var reg = new Regex(@"[^\w(@)\|&]");
searchString = reg.Replace(searchString, "");
searchString = searchString.Trim();
if (searchString == "")
return null;
//The search string
var dt = new DataTable();
using (
var connection = new SqlConnection(
ConfigurationManager.ConnectionStrings[
"DBConnection"].ConnectionString))
{
var userDataset = new DataSet();
var myDataAdapter = new SqlDataAdapter(
"SELECT TOP(20) * FROM FREETEXTTABLE(dbo.Names, *, @param) AS r INNER JOIN Names ON r.[KEY] = Names.MyKey order by RANK DESC",
connection);
myDataAdapter.SelectCommand.Parameters.Add("@param", SqlDbType.VarChar, 255);
myDataAdapter.SelectCommand.Parameters["@param"].Value = searchString;
myDataAdapter.Fill(dt);
}
return dt;
}
搜索函数使用 web.config 中定义的连接字符串,如下所示:
<configuration>
<connectionStrings>
<add name="DBConnection"
connectionString="Data Source=localhost;Initial Catalog=Search;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
此网页的结果如下:
自动完成搜索
当您在输入搜索字符串时,使用预搜索功能以获得即时搜索通常很有用。这可以通过使用 AJAX 组件和 WebService 来实现。
首先创建一个 WebService。Ajax 中的 AutoCompleteExtender 需要一个字符串数组。因此,创建的方法应该编译结果字符串。在此示例中,字符串编译为 NAME "Email@Email",描述被跳过,因为这可能会很长。
通过右键单击解决方案并选择“添加”、“新建项”并选择 WebService 来创建一个新的 WebService。为了能被 AutoCompleteExtender 使用,重要的是 WebService 方法声明必须完全如下:
public string[] Search(string prefixText, int count)
这是示例的完整代码,该方法使用与前面数据库搜索相同的方法。但是,它只使用其中两列作为信息。
using System.Data;
using System.Web.Services;
namespace WebTest
{
/// <summary>
/// Summary description for WebService1
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
public class SearchService : System.Web.Services.WebService
{
public SearchService()
{
}
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public string[] Search(string prefixText, int count)
{
var searchString = prefixText;
DataTable dt = search.SearchDB(searchString);
if (dt == null)
return null;
var countormax = dt.Rows.Count > count ? count : dt.Rows.Count;
var result = new string[countormax];
for (int i = 0; i < countormax; i++)
{
result[i] = dt.Rows[i][3].ToString() + " \"" +
dt.Rows[i][4].ToString() + '"';
}
return result;
}
}
}
最后一步是将 AutoCompleteExtender 添加到搜索框中。WebService 页面及其 WebService 方法被定义到扩展器的 ServicePath 和 ServiceMethod 属性。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="search.aspx.cs" Inherits="WebTest.search" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="asp" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Search Site</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="SearchBox" runat="server"></asp:TextBox>
<asp:FilteredTextBoxExtender ID="SearchBox_FilteredTextBoxExtender"
runat="server" Enabled="True"
TargetControlID="SearchBox" ValidChars="*&| ().@åäöÅÄÖ" FilterType="Custom, Numbers, LowercaseLetters, UppercaseLetters">
</asp:FilteredTextBoxExtender>
<asp:AutoCompleteExtender ID="SearchBox_AutoCompleteExtender" runat="server"
DelimiterCharacters="" Enabled="True" ServicePath="~/SearchService.asmx" MinimumPrefixLength="1"
TargetControlID="SearchBox" CompletionSetCount="5" ServiceMethod="Search" EnableCaching="False">
</asp:AutoCompleteExtender>
<asp:Button ID="SearchButton" runat="server" Text="Search"
OnClick="SearchButton_Click" />
<asp:Table ID="ResultTable" runat="server">
</asp:Table >
<asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server"></asp:ToolkitScriptManager>
</div>
</form>
</body>
</html>
总结
本文展示了如何在 SQL Server 2012 中创建具有全文索引的数据库表。然后,它继续展示了如何在 Asp.NET 页面中实现强大的排名全文搜索,包括 `AutoComplete` 功能。