服务器使用linq和c#评估了对多个关键字的搜索



这似乎是一项微不足道的任务,但在stackoverflow和互联网上搜索却未能找到一个好的解决方案。

我正在实现以字符串作为输入并提取关键字的简单搜索功能。下一步是将关键字应用到查询中。我试着在下面编写代码,但linq在使用时抛出了一个从服务器评估更改为客户端评估的错误。可枚举

但我想在服务器上进行评估,我想在一次往返中尽可能多地进行评估。我想我的代码中的问题与有关。任何/。包含模式,但我希望有人能帮上忙。

public class Person
{
public string Name { get; set; }
public string Email { get; set; }
}
List<string> keywords = new List<string> { "John", "Doe" };
IEnumerable<Person> persons = await context.PersonDbSet
.Where(person => keywords.Any(keyword => person.Name.Contains(keyword)) 
|| keywords.Any(keyword => person.Email.Contains(keyword)))
.ToListAsync();

It seems like a trivial task,它不是,它是效率最高的查询之一。这就是为什么几乎所有的自动完成筛选器都使用StartsWith而不是Contains。或者使用像Elastic这样的专用全文搜索引擎。

看起来您要做的是生成一系列由OR组合的LIKE '%..%'子句。像这样的子句不能利用任何索引,所以最终会扫描整个表。

而布尔代数中的CCD_ 4。相当于多个OR表达式,EF Core无法进行这样的翻译。SQL有自己的ALLANY关键字,它们引用结果集中的所有元素,而不是所有条件。

要生成所需的查询,您必须显式指定表达式,或者使用像LINQKit这样的库使代码更加简洁。它仍将执行全表扫描:

IQueryable<Person> SearchPersons (params string[] keywords)
{
var predicate = PredicateBuilder.New<Person>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Name.Contains (temp))
.Or (p => p.Email.Contains (temp));
}
return dataContext.Persons.AsExpandable().Where (predicate);
}
...
var persons=await SearchPersons(keywords).ToListAsync();

使用全文搜索

若要使此查询高效,您必须使用SQL Server的全文搜索索引。CONTAINS运算符可用于搜索一个或多个字段中的单词、另一个字段附近的单词等。示例显示以下查询是可能的:

SELECT * FROM PERSONS WHERE CONTAINS( (Name,Email), 'John')

SELECT * FROM PERSONS WHERE CONTAINS( Name , 'John OR Doe')

所以我想两者都可以合并

SELECT * FROM PERSONS WHERE CONTAINS((Name,Email) , 'John OR Doe')

在EF Core中执行相同操作可以在EF中执行。包含并组合所有关键字与OR:

IQueryable<Person> SearchPersons (params string[] keywords)
{
var term=String.Join(" OR ",keywords);
return dataContext.Where( p => EF.Contains(p.Email,term) 
|| EF.Contains(p.Name,term));
}

您也可以使用FromSqlRaw来指定您想要的条件:

var query="SELECT * FROM PERSONS WHERE CONTAINS((Name,Email) , @term)";
var term=String.Join(" OR ",keywords);
var query=dataContext.Persons.FromSqlRaw(query,term);

您可以使用。包含名称,但不同时包含电子邮件,请尝试此

var persons = await context.PersonDbSet
.Where(person => 
EF.Functions.Like(person.Name, "%John%")
|| EF.Functions.Like(person.Name, "%Doe%")
|| EF.Functions.Like(person.Email, "%John%")
|| EF.Functions.Like(person.Email, "%Doe%")
).ToListAsync();

最新更新