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

Server evaluated search for multiple keywords using linq and c#

看似微不足道的小事,在Whosebug和网上搜索都没有找到好的解决方案。

我正在实现简单的搜索功能,将字符串作为输入并提取关键字。下一步是将关键字应用到查询中。我尝试在下面编写代码,但 linq 抛出一个错误,使用 .AsEnumerable

从服务器评估更改为客户端评估

但我想在服务器上进行评估,并且我想在一次往返中尽可能多地进行评估。我想我的代码中的问题与 .Any/.Contains 模式有关,但我希望有人能提供帮助。

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();

您可以将 .Contains 与名称一起使用,但不能同时与电子邮件一起使用,试试这个

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();

It seems like a trivial task,它不是,它是最低效的查询之一。这就是几乎所有自动完成过滤器都使用 StartsWith 而不是 Contains 的原因。或者使用像 Elastic 这样的专用全文搜索引擎。

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

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

要生成您想要的查询,您必须明确指定表达式,或者使用像 LINQKit 这样的库来使代码更简洁。它仍将执行完整的 table 扫描:

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 服务器的 Full Text Search indexes. The 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.Contains 并将所有关键字与 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);