65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (10投票s)

2012年8月20日

LGPL3

7分钟阅读

viewsIcon

60880

downloadIcon

2250

描述如何使用全文搜索和 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` 功能。

© . All rights reserved.