拆分后如何使用实体框架搜索文本?

问题描述

我有一个实体模型

public class User{
   public string FirstName {get;set;}
   public string LastName {get;set;}
   public string Department {get;set;}
}

所以我想使用实体框架核心3.1在数据库搜索类似"john smith"的文本。

我以前是在分割文字

    public async Task<IEnumerable<UserListviewmodel>> Search(string search)
    {
        var terms = search.Split(" ");

        var queryable = _context.Users.Where(s => terms.All(m => s.Department.ToLower().Contains(m)) ||
                                                  terms.All(m => s.FirstName.ToLower().Contains(m)) ||
                                                  terms.All(m => s.LastName.ToLower().Contains(m))).AsQueryable();

        ...........
        ...........
        ...........
    }

但是它不起作用。

那我该怎么办?

解决方法

在大多数情况下,EF Core 3.x并不真正支持AllAny的翻译,并且您的代码略有错误,我认为您真正想要的是:

var queryable = _context.Users.Where(u => terms.All(m => u.Department.Contains(m) ||
                                                         u.FirstName.Contains(m) ||
                                                         u.LastName.Contains(m)));

由于无法翻译,因此您需要将其重新格式化为可以的代码。

借助LINQKit,您可以使用PredicateBuilder创建扩展名,该扩展名将针对每个术语将查询重新映射为一系列&&测试:

// searchTerms - IEnumerable<TKey> where all must be in a row's key
// testFne(row,searchTerm) - test one of searchTerms against a row
// dbq.Where(r => searchTerms.All(s => testFne(r,s)))
public static IQueryable<T> WhereAll<T,TKey>(this IQueryable<T> dbq,IEnumerable<TKey> searchTerms,Expression<Func<T,TKey,bool>> testFne) {
    var pred = PredicateBuilder.New<T>();
    foreach (var s in searchTerms)
        pred = pred.And(r => testFne.Invoke(r,s));

    return dbq.Where((Expression<Func<T,bool>>)pred.Expand());
}

您将使用以下方式:

var queryable = _context.Users
                    .WhereAll(terms,(u,m) => u.Department.Contains(m) ||
                                       u.FirstName.Contains(m) ||
                                       u.LastName.Contains(m));

对于“约翰·史密斯”,扩展方法将创建以下内容:

var queryable = _context.Users
                    .Where(u => (u.Department.Contains("john") ||
                                 u.FirstName.Contains("john") ||
                                 u.LastName.Contains("john")) &&
                                (u.Department.Contains("smith") ||
                                 u.FirstName.Contains("smith") ||
                                 u.LastName.Contains("smith"))
                           );
,

我认为您不需要terms.All。因为“约翰·史密斯”是全部的全名,因此不会在名字或姓氏字段中找到。

我不确定是否可以进行以下操作。

var queryable = _context.Users.Where(s => terms.Contains(m => s.Department.ToLower().Contains(m)) &&
                                                  terms.Contains(m => s.FirstName.ToLower().Contains(m)) ||
                                                  terms.All(m => s.LastName.ToLower().Contains(m))).AsQueryable();

尽管它不太准确,但也以这种方式返回“ john john”,但这很罕见。

,

接下来如何?

  void Main()
  {
    var users = new List<User>
    {
        new User { FirstName = "John",LastName = "Smith",Department = "Web" },new User { FirstName = "Aaliyah",LastName = "Lin",Department = "Warehouse" },new User { FirstName = "Cristian",LastName = "Stone",Department = "Cleaning" },new User { FirstName = "Kierra",LastName = "Davidson",Department = "Mobile" },new User { FirstName = "Lizbeth",LastName = "Gregory",Department = "Web" }
    };
    
    var search = "Lizbeth Gregory";
    var terms = search.ToLower().Split(' ');
    users.Where(s => terms.All(m => s.Department.ToLower().Contains(m)) ||
                                                      (terms.Any(m => s.FirstName.ToLower().Contains(m))) ||
                                                      terms.Any(m => s.LastName.ToLower().Contains(m)))
                                                      .Dump();
}

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Department { get; set; }
}
,

另一种解决方案是汇总您的搜索词:

var terms = search
    .Split(' ',StringSplitOptions.RemoveEmptyEntries);
query = terms
    .Aggregate(query,(current,term) =>
         current.Where(x =>
             x.FirstName.Contains(term)
             || x.LastName.Contains(term)
             || x.Department.Contains(term)
         )
    );