搜索词来自UI,用于搜索表的实体。这些搜索结果应在用户界面中显示的顺序如下:
所以我首先从数据库获得了实体:
result = entities.Where(e => e.Name.Contains(searchTerm)).ToList();
然后我在内存中重新排列它们:
var sortedEntities = result.Where(e => e.Name.ToLower() == searchTerm.ToLower())
.Union(result.Where(e => e.Name.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase)))
.Union(result.Where(e => e.Name.Contains($" {searchTerm} ")))
.Union(result.Where(e => e.Name.EndsWith(searchTerm, StringComparison.OrdinalIgnoreCase)))
.Union(result.Where(e => e.Name.Contains(searchTerm)));
在添加分页之前,它一直运行良好。现在,如果第2页上有完全匹配项(来自DB的数据),则不会首先显示。
我能想到的唯一解决方案是分离请求(在这种情况下为5个请求)并手动跟踪页面大小。我的问题是,有没有一种方法可以告诉DB遵守该顺序,并在一次DB旅行中获得排序的数据?
答案 0 :(得分:1)
我花了一些时间才意识到您使用Union
来尝试通过“匹配强度”对数据进行排序:首先是完全匹配的数据,然后是大小写不同的数据,等等。当我看到带有谓词的Union
,我的巴甫洛夫条件思维将其转换为OR
。我不得不从thinking fast to slow切换。
因此,问题在于没有可预测的排序。毫无疑问,链接的Union
语句确实会产生确定的最终排序顺序,但这不一定是Union
的顺序,因为每个Union
还会执行一个隐式的Distinct
。一般规则是,如果要特定的排序顺序,请使用OrderBy
方法。
话虽如此,然后...
var result = entities
.Where(e => e.Name.Contains(searchTerm))
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToList();
...期望的结果似乎可通过以下方式获得:
var sortedEntities = result
.OrderByDescending(e => e.Name == searchTerm)
.ThenByDescending(e => e.Name.ToLower() == searchTerm.ToLower())
.ThenByDescending(e => e.Name.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase))
... etc.
(下降,因为false
命令before
为真)
但是,如果匹配项多于pageSize
,则排序将为时已晚。如果pageSize = 20
和项目21是第一个完全匹配项,则该项目将不在页面1上。这意味着:应在 分页之前进行排序。
第一步是从第一条语句中删除.ToList()
。如果删除它,则第一条语句是IQueryable
表达式,并且Entity Framework能够将完整的语句组合为一个SQL语句。下一步是将Skip/Take
移到完整语句的末尾,它也将成为SQL的一部分。
var result = entities.Where(e => e.Name.Contains(searchTerm));
var sortedEntities = result
.OrderByDescending(e => e.Name == searchTerm)
.ThenByDescending(e => e.Name.ToLower() == searchTerm.ToLower())
.ThenByDescending(e => e.Name.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase))
... etc
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToList();
但是现在出现了一个新问题。
由于不支持与StringComparison.OrdinalIgnoreCase
进行字符串比较,因此对于部分语句,Entity Framework将自动切换到client-side evaluation。所有过滤的结果将从数据库中返回,但是大多数排序和所有分页都将在内存中完成。
当滤镜很窄时,这可能不是很糟糕,但当滤镜很宽时,这是非常糟糕的。因此,最终,要做到这一点,您必须删除StringComparison.OrdinalIgnoreCase
并以稍差的匹配强度来解决。带我们去
最终结果:
var result = entities.Where(e => e.Name.Contains(searchTerm));
var sortedEntities = result
.OrderByDescending(e => e.Name == searchTerm)
.ThenByDescending(e => e.Name.StartsWith(searchTerm))
.ThenByDescending(e => e.Name.Contains($" {searchTerm} "))
.ThenByDescending(e => e.Name.EndsWith(searchTerm))
.ThenByDescending(e => e.Name.Contains(searchTerm))
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToList();
为什么“不够精致”?因为根据您的评论,数据库排序规则不区分大小写,所以SQL无法在不添加COLLATE
语句的情况下,按大小写区分完全匹配。这是我们用LINQ无法做到的。