我们正在对公司名称字段使用全文索引搜索。
我们使用EF作为数据层,我被要求不要使用存储过程。
这是我的数据访问层的方法:
public Task<List<Company>> SearchByName(string searchText)
{
return DataContext.Company.SqlQuery(
"select CompanyId AS Id, * from Company.Company AS c where contains(c.Name, @SearchText)",
new SqlParameter("@SearchText", ParseSearchTextForMultiwordSearch(searchText)))
.ToListAsync();
}
我们想在搜索中拆分单词,然后将它们连接在一起进行and搜索。这意味着像"我的公司"这样的查询实际上是根据索引搜索"我的"one_answers"公司"。
这段代码为上面的select查询合并术语。
public string ParseSearchTextForMultiwordSearch(string searchText)
{
var words = GetValidSearchTerms(searchText);
var quotedWords = words.Select(x => string.Format(""{0}*"", x));
return string.Join(" AND ", quotedWords);
}
一切都很好,直到你开始添加"关键字"。到目前为止,我们已经计算出和、或、或不包含在搜索中返回0个结果。没有错误,只是没有结果。
这是我们将某些单词"列入黑名单"的方法,因此它们被排除在搜索查询之外。
private static List<string> GetValidSearchTerms(string searchText)
{
//AND and OR are keywords used by SQL Server Full Text Indexing.
var blacklist = new string[] {
"and",
"or",
"not"
};
//Filter them out here
var words = searchText.Split(' ');
var validWords = words.Where(x => !blacklist.Contains(x));
return validWords.ToList();
}
问题是,我们刚刚发现了另一个"关键字"似乎导致了这个问题。"Do"不会产生任何结果。我可以把它添加到黑名单中,但随着这个东西的增长,我开始觉得处理它的方式是错误的。
有更好的方法来处理这个问题吗?
编辑:其他几个场景
如果我根本不处理搜索字符串,那么搜索单词"not"将导致错误"Null或空全文谓词"。
同样的场景,只是按原样应用字符串,如果我创建一个公司"company Do Not Delete",任何具有Do或Not的字符串版本都返回0结果。
自从我发布这个问题以来已经有一段时间了,经过几次迭代,我想出了一些适合我们需求的搜索逻辑。
首先,我们有一个业务规则,它要求搜索包含一个&号。全文索引似乎省略了&,使得返回的结果不正确。因此,我必须对任何&搜索使用类似语句。
我留下我的代码做做如上所述,它解析出单词的黑名单,并尝试包含搜索。如果由于任何原因失败,我将执行FREETEXT搜索。
public async Task<List<Company>> SearchByName(string searchText)
{
var results = new List<Company>();
if (string.IsNullOrWhiteSpace(searchText))
return results;
if (searchText.IndexOf("&") >= 0)
{
var likeQuery = string.Format("%{0}%", searchText);
results = await DataContext.Company.SqlQuery("SELECT CompanyId AS Id, IsEligible AS IsReadOnly, *" +
" FROM Company.Company AS con" +
" WHERE con.Name LIKE @SearchText",
new SqlParameter("@SearchText", likeQuery))
.ToListAsync();
}
else
{
var terms = ParseSearchTextForMultiwordSearch(searchText);
if (string.IsNullOrWhiteSpace(terms))
return results;
// SqlQuery does not take any column mappings into account (https://entityframework.codeplex.com/workitem/233)
// So we have to manually map the columns in the select statement
var sqlQueryFormat = "SELECT CompanyId AS Id, IsEligible AS IsReadOnly, *" +
" FROM Company.Company AS con" +
" WHERE {0}(con.Name, @SearchText)";
var sqlQuery = string.Format(sqlQueryFormat, "CONTAINS");
var errored = false;
try
{
results = await DataContext.Company.SqlQuery(sqlQuery,
new SqlParameter("@SearchText", terms))
.ToListAsync();
}
catch
{
//catch the error but do nothing with it
errored = true;
}
//when the contains search fails due to some unknown error, use Freetext as a backup
if (errored)
{
sqlQuery = string.Format(sqlQueryFormat, "FREETEXT");
results = await DataContext.Company.SqlQuery(sqlQuery,
new SqlParameter("@SearchText", terms))
.ToListAsync();
}
}
return results;
}