我的搜索算法没有产生预期的结果。
例如:数据库中有一个项目,名称为“Vitamin C,askorbinsyre”。算法在搜索“vi”、“it”、“in”、“vit”、“as”和“re”时发现这一点,但在搜索“ta”、“vita”、“ask”、“ yr”或“yre”!这对我来说很奇怪。
这是我的代码(服务的一部分
SearchService
):
public async Task<List<NutrientDisplayDTO>> Search(string searchTerm)
{
var nutrientsResults = await SearchEntity<Nutrient>(searchTerm);
// more entities here (all returned as a tuple inside a service response object,
// but I have changed it to only do a single entity search here for brevity)
return _mapper.Map<List<NutrientDisplayDTO>>(nutrientsResults);
}
private async Task<List<T>> SearchEntity<T>(string searchTerm) where T : class
{
// The search component is replacing " " with "+"
string[] searchWords = searchTerm
.Split('+', StringSplitOptions.RemoveEmptyEntries)
.ToArray();
var filteredEntities = await _context.Set<T>()
.ToListAsync();
var predicate = BuildSearchPredicate<T>(searchWords);
return filteredEntities
.Where(predicate)
.ToList();
}
private static Func<T, bool> BuildSearchPredicate<T>(string[] searchWords)
{
return entity =>
{
var entityType = typeof(T);
var propertyName = "Name";
var property = entityType.GetProperty(propertyName);
if (property == null)
return false;
var propertyValue = property.GetValue(entity);
if (propertyValue == null || !(propertyValue is string))
return false;
var entityNameWords = ((string)propertyValue).ToLower().Split(' ');
// Check if any word in the entity name matches any of the search words
return searchWords.Any(searchWord =>
entityNameWords.Any(entityWord =>
entityWord.Contains(searchWord.ToLower())));
};
}
属于搜索一部分的实体都实现了
ISearchableEntity
接口:
public interface ISearchableEntity
{
string Name { get; }
}
我必须承认,我在这方面得到了很多人工智能帮助,但我并不真正理解任何谓词内容。
你能帮忙吗?
第一个
_context.Set<T>()
.ToListAsync();
将从数据库中获取所有行,并在内存中进行所有处理。这是典型的“坏事”。但大多数 SQL 数据库无法使用索引来检查字符串是否包含单词,因此无论如何都需要进行全表扫描才能完成您想做的事情。请记住,如果您有大量文本要搜索,您可能应该使用一些专门的搜索框架。
var entityType = typeof(T);
var propertyName = "Name";
var property = entityType.GetProperty(propertyName);
if (property == null)
return false;
var propertyValue = property.GetValue(entity);
if (propertyValue == null || !(propertyValue is string))
return false;
这使用了大量的反射,这是降低性能的另一种好方法。更好的方法是让调用者定义要使用的属性,即
private static Func<T, bool> BuildSearchPredicate<T>(Func<T, string> selector, string[] searchWords){
return entity =>
{
var stringToSeach = selector(entity);
下一期
var entityNameWords = ((string)propertyValue).ToLower().Split(' ');
如果您只是要检查任何实体单词是否包含任何搜索单词,我认为拆分字符串没有什么意义。只需在整个字符串上运行 contains 即可。
searchWords.Any(word => stringToSeach.Contains(word, StringComparison.CurrentCultureIgnoreCase));
请注意
StringComparison.CurrentCultureIgnoreCase
的用户,这通常比将字符串转换为较低值更好,因为您可以避免额外的内存分配。旧版本的 .Net 缺少这样的 Contains 方法,在某些情况下 IndexOf
可以用作替代方法。
我不会创建谓词函数,而是让该方法直接应用过滤,即类似
public static IEnumerable<T> WherePropertyContainsAny<T>(IEnumerable<T> values, Func<T, string> selector, params string[] searchWords)
{
return values.Where(
t =>
{
var str = selector(t);
return searchWords.Any(word => str.Contains(word, StringComparison.CurrentCultureIgnoreCase));
});
}
为此类函数编写单元测试通常也是一个好主意,这样您就可以有效地检查问题并调试代码:
[Test]
public void Test()
{
var input = new []{"Vitamin C, askorbinsyre", "random string"};
var numberOfMatches = WherePropertyContainsAny(input, s => s, "ta", "vita", "ask", "yr", "yre").Count();
Assert.AreEqual(1, numberOfMatches);
}