实体框架和全词匹配-分页和过滤结果

本文关键字:分页 过滤 结果 框架 实体 | 更新日期: 2023-09-27 18:06:39

背景

我正在使用ASP。NET MVC4、SQL Server 2008 R2和实体框架5。

该网站接受一个分隔的关键字列表来搜索数据库内容。它还需要将结果分页给用户(目前每页100个结果(。

这一直进行得很顺利,直到有人要求关键字搜索不是用部分匹配,而是用全词匹配。

问题

在我已经返回结果之后执行完整的单词匹配意味着我可能没有结果的query.Pagesize要显示——这会打乱UI页面。在第一页上来自SQL Server的100个部分匹配中,20个可能会随着整个单词处理而被删除。

我目前正在使用LINQ构建我的查询,并对以下关键字进行and搜索:

// Start with all the MyItems
var results = UnitOfWork.MyItemRepository.GetAll();
// Loop the keywords to AND them together
foreach(var keyword in query.Keywords)
{
    var keywordCopy = keyword;
    // Look for a hit on the keyword in the MyItem
    results = results.Where(x => x.Title.Contains(keywordCopy));
}

稍后,在获取结果总数、分页和执行查询时:

var totalCount = results.Count();
// Page the results
results = results.Skip((query.Page - 1) * query.Pagesize).Take(query.Pagesize);
...
// Finalize the query and execute it
var list = results.ToList();

因为我需要进行全词匹配,而不是部分匹配,所以我使用正则表达式处理关键字,并从list中删除不匹配项。

var keywordsRegexPattern = "^" + string.Concat(query.Keywords.Select(keyword => string.Format(@"(?=.*'b{0}'b)", Regex.Escape(keyword))));
foreach(var item in list.ToList())
{
    var searchableData = some combined string of item data
    // See if the keywords are whole word matched in the combined content
    var isMatch = Regex.IsMatch(searchableData, keywordsRegexPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
    // If not a match, remove the item from the results
    if(!isMatch)
    {
        list.Remove(item);
    }
}
// Change list into custom list of paged items for the UI
var pagedResult = new PagedList<MyItem>(list, query.Page, query.Pagesize, totalCount);
return pagedResult;

问题

有人知道用EF进行全词匹配并进行结果分页的方法吗?

我想出但不喜欢的想法:

  1. 大块的结果。返回100个结果,删除20个部分关键字匹配,再获取20个,重复。这可能会导致执行多个查询,而同时获取所有数据会更快。这也意味着它将窃取下一页的潜在结果,而下一页必须用某种偏移量进行跟踪。

  2. 返回所有行(无SQL分页(,然后用C#进行处理和分页。每次都能得到所有结果,这似乎很糟糕。

实体框架和全词匹配-分页和过滤结果

我看到了两种选择(我可能会错过一些更容易的东西,但无论如何(

要么使用string.Contains(keyword),从数据库中检索所有相应的数据,然后用精确匹配进行筛选,并对枚举的结果进行分页(这样你可能会从数据库中得到"不太多结果"(。

另一种方式:

foreach(var keyword in query.Keywords)
{
    //add space at start or end of keyword for contains
    var containsKeyword = string.Format(" {0} ", keyword);
    //add space at end only for startsWith
    var startsWithKeyword = string.Format("{0} ", keyword);
    //add space at start only for endsWith
    var endsWithKeyword = string.Format(" {0}", keyword);
    // Look for a hit on the keyword in the MyItem
    results = results.Where(x => x.Title.Contains(containsKeyword) || x.Title.StartsWith(startsWithKeyword) || x.Title.EndsWith(endsWithKeyword));
}