使用LINQ进行全文搜索

4
我正在尝试实现一个搜索功能,搜索结果可以填充到GridView中。我在C#中使用LINQ查询我的数据库。
问题是,如果用户想要通过多个列/字段进行搜索,并且有多个搜索项,那么我该怎么办?例如,如果我搜索包含以下三个字段的表,并且有一些数据: > firstName | lastname | players# >>1. Michael | Jordan | 12 >>2. Michael | Jordan | 24 >>3. Michael | Jordan | 45 >>4. DeAndre| Jordan | 6 >>5. Jerome| Jordan | 44 >>6. Jordan| Sparks| 88
现在,如果我搜索Jordan,则会得到所有内容:
如果我搜索Michael Jordan,则应该返回行#1,2,3。
如果我搜索Jordan Sparks 88,则应该返回行#6。
因此,我的问题是我不知道搜索词可能出现在哪个列/字段中,所以必须搜索所有列/字段。在我的当前代码中,我遍历每个列/字段名并使用Contains()然后||(“or”),但它仅适用于一个搜索项。
是否有一种优雅而简单的方法来搜索和过滤整个LINQ表,以使列表最小化?从那里,我将将该结果添加到数据源,然后绑定它。
我正在使用一个典型的GridView。它看起来像这样:
    <asp:GridView ID="GridView" runat="server" AllowSorting="True" PagerStyle-Mode="NumericPages"
                                AutoGenerateColumns="false" Width="100%" CssClass="gridView" OnPageIndexChanging="GridView_PageIndexChanging"
                                AllowPaging="True"  DataKeyNames="idPlayersList" OnRowCommand="GridView_RowCommand"
                                OnRowEditing="GridView_RowEditing" OnRowCancelingEdit="GridView_CancelEditRow"
                                OnRowUpdating="GridView_UpdateRow" OnRowDataBound="GridView_RowDataBound">
                                <RowStyle CssClass="rowStyle"></RowStyle>
                                    <asp:BoundField DataField="FirstName" HeaderText="First Name" SortExpression="FirstName" />
                                    <asp:BoundField DataField="LastName" HeaderText="Last Name" SortExpression="LastName" />
<asp:BoundField DataField="PlayersNumber" HeaderText="Players Number" SortExpression="PlayersNumber" />
<asp:TemplateField HeaderText="Team" SortExpression="Team">
                                    <EditItemTemplate>
                                        <asp:DropDownList ID="ddlTeam" runat="server" CssClass="dropdown" AutoPostBack="True"
                                            AppendDataBoundItems="true" DataTextField="TeamName" DataValueField="idTeam">
                                        </asp:DropDownList>
                                    </EditItemTemplate>
                                    <ItemTemplate>
                                        <asp:Label ID="lblTeam" runat="server" Text='<%# Bind("TeamName") %>'></asp:Label>
                                    </ItemTemplate>
                                </asp:TemplateField>

我的当前搜索功能大致如下:

/**
* This method is for button search functionality 
* 
*/
protected void btnSearch_Click(object sender, EventArgs e)
{

    // Call to Entity Model Framework
    DBModel.DBEntities context = new DBModel.DBEntities();
    
    string[] searchTerms = txtSearch.Text.Trim().Split('&');

//Prepare to build a "players" query:
IQueryable<DBModel.playersList> playersListQuery = context.playersLists;

foreach (var term in searchTerms)
{
    //Refine our query, one search term at a time:
    playersListQuery = playersListQuery.Where(p => p.isDeleted == false && (p.FirstName.Contains(term.Trim()) ||
                                           p.LastName.Contains(term.Trim()) ||
                                           p.PlayersNumber.Contains(term.Trim()) ||
                                           p.Team.Name.Contains(term.Trim())));
}

//Now we have the complete query. Get the results from the database:
var filteredplayersList = playersListQuery.Select(s => new
                                           {

                                               idPlayersList = s.idPlayersList,
                                               FirstName = s.FirstName,
                                               LastName = s.LastName,
                                               PlayersNumber  = s.PlayersNumber,
                                               TeamName  = s.Team.Name
                                            }).ToList();


GridView.DataSource = filteredplayersList; //Connecting query to the datasource Gridview
GridView.DataBind();  //Binding Gridview
}

可能是重复的问题:是否可以在LINQ中使用全文搜索(FTS)? - Sruit A.Suk
2个回答

6
幸运的是,在Linq2SQL中创建多个AND查询(每个搜索项都有一个)非常容易——只需在查询中不断添加.Where(..即可:
string[] searchTerms = "Jordan Sparks 88".Split(' ');

using (var dc = new MyDataContext())
{
    //Using DataContext.Log is handy 
    //if we want to look at Linq2SQL's generated SQL:
    dc.Log = new System.IO.StringWriter();

    //Prepare to build a "players" query:
    IQueryable<Player> playersQuery = dc.Players;

    //Refine our query, one search term at a time:
    foreach (var term in searchTerms)
    {
        //Create (and use) a local variable of the search term
        //to avoid the "outer variable trap":
        //https://dev59.com/vHA75IYBdhLWcg3wOGLS
        //https://dev59.com/W3VC5IYBdhLWcg3wbglT
        var currentTerm = term.Trim();
        playersQuery = playersQuery.Where(p => (p.isDeleted == false)
                                                &&
                                               (p.FirstName.Contains(currentTerm) ||
                                                p.LastName.Contains(currentTerm) ||
                                                p.PlayersNumber.Contains(currentTerm) ||
                                                p.Team.Name.Contains(currentTerm))
                                            );
    }

    //Now we have the complete query. Get the results from the database:
    var filteredPlayers = playersQuery.Select(p => new
                                                   {
                                                       p.idPlayer,
                                                       p.FirstName,
                                                       p.LastName,
                                                       p.PlayersNumber,
                                                       TeamName = p.Team.Name
                                                   })
                                      .ToArray();

    //See if the generated SQL looked like it was supposed to:
    var sql = dc.Log.ToString();
}

如果我理解正确的话,它将查询表格 searchTerm 数组大小的次数。在循环的每次迭代中,它会生成一个表格,在下一次迭代中,会产生一个更小的过滤表格,即 filteredPlayers 变量。 - user2533789
不,这就是 Linq2SQL 的美妙之处:foreach 循环仅仅是 准备 了一个查询(一个 IQueryable)。只有当你请求 IQueryable 中的元素时,才会实际查询数据库表,在最后调用 .ToArray() 时隐式地发生。运行代码并查看 sql 字符串 - 只有一个 SQL 查询(带有 X 个 AND 语句)。 - Sphinxxx
如果是这样的话,我会得到一个类似于这样的错误:Sys.WebForms.PageRequestManagerServerErrorException: Sys.WebForms.PageRequestManagerServerErrorException: DataBinding: 'DBModel.Players' 不包含名为 'TeamName' 的属性。我正在尝试将其绑定到一个GridView上。 GridView.DataSource = filteredRangeList; GridView.DataBind(); - user2533789
1: 是的,你写的FK查询应该可以工作。 2: 我只是猜测,但那个错误似乎来自于GridView,而不是SQL查询。这个网格是否设置了一个查找“TeamName”属性的列?如果是这样,DBModel.Players类需要有一个名为“TeamName”的属性。这是什么类型的GridView?WinForms? - Sphinxxx
好的。首先,您应该检查数据库查询本身是否返回正确的玩家(例如我的示例中的filteredPlayers)。如果是这样,问题必须发生在稍后,当您将玩家添加/绑定到GridView时。恐怕我没有任何使用Asp.NET GridView Web控件的经验,所以我不知道为什么它突然开始寻找“TeamName”属性。 - Sphinxxx
显示剩余15条评论

0
如果您只需要对所有属性使用Contains条件,您可以将需要的所有属性封装到一个单独的属性中,然后您可以应用更少字段的LINQ表达式,看起来更清晰。
public class Player
{
    public string SearchField { get => string.Format("{0}{1}{2}{3}",FirstName,LastName,PlayersNumber,Team); }

    public string FirstName {get; set;}

    public string LastName {get; set;}

    public string PlayersNumber {get; set;}

    public string Team {get; set;}
}

更好的做法是建议您在创建对象实例时在构造函数中设置SearchField,这将提高性能,避免为每次调用创建子字符串。
public string SearchField { get; private set; }

public Player(string firstName, string lastName, playersNumber, string team)
{
    FirstName = firstName;
    LastName = lastName;
    PlayersNumber = playersNumber;
    Team = team;
    SearchField = string.Format("{0}{1}{2}{3}",firstName,lastName,playersNumber,team);
}

然后你只需要按照SearchField属性进行过滤

 foreach (var term in searchTerms)
 {
     var currentTerm = term.Trim();
     playersQuery = playersQuery.Where(p => p.isDeleted == false && 
                                            p.SearchField.Contains(currentTerm));
 }

现在你已经有了一个只有少数字段的LINQ表达式,可以得到完全相同的结果集。

还有一件事,我刚刚看到你提到你正在使用LINQ to SQL查询,你可以通过在SQL表中创建一个计算列来应用相同的方法,该列可以将多个列的值连接起来,你可以在SQL Server 2012中实现这一点。

希望有人会发现这个解决方案有用。

问候!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接