使用LINQ进行宽容/模糊搜索

12

我正在尝试对我继承的数据库实现搜索功能。要求是用户必须能够通过名称搜索对象。不幸的是,一个对象可能有多个与之关联的名称。例如:

ID    Name
1     John and Jane Doe
2     Foo McFoo
3     Boo McBoo
在每个记录中只存在单个名称时,实现搜索非常容易:
var objects =  from x in db.Foo
               where x.Name.Contains("Foo McFoo")
               select x;

然而,当存在多个名称时,这种方法就不再适用。

问题:是否可能编写一个搜索方法,在某人使用搜索词John DoeJane Doe时返回记录一(John和Jane Doe)?


1
你能否对空格进行字符串分割,将搜索字符串拆分开来,然后使用.Contains运行多个查询并返回所有结果? - Jesse Carter
如果有一个“John Smith”怎么办?你会把它拆开,然后搜索每个名字的部分吗?什么构成了名字的姓和名?我的意思是,在当前形式下,这个名字似乎没有结构。 - hometoast
4个回答

10

这样做会影响性能,但这个快速方法怎么样:

string[] filters = "John Doe".Split(new[] {' '});
var objects =  from x in db.Foo
               where filters.All(f => x.Name.Contains(f))
               select x;

看起来返回了您所期望的结果。现在,当您同时拥有记录“John Doe”和“John and Jane Doe”时,您可以调整它以表现得更好。

这对您有效吗?


1
+1,它确实有效!我的担忧是.All()的性能。也许这是在当前数据库设置下唯一的方法。在我采取行动之前,我很想看到社区对这种方法的反应... - James Hill
1
好吧,如果没有All(),你只能进行标准搜索(速度取决于数据库配置等因素)。使用All()解决方案,平均会使速度乘以2-3倍(如果名称通常只有一个FirstName和一个LastName)。因此,在确切匹配的情况下,您不必拆分字符串,这会影响速度。首先尝试使用简单搜索,如果没有结果,则考虑使用All()。只是随便想想。 - Vladimir

9
您可以创建一个名为“ContainsFuzzy”的自定义扩展方法:
public static bool ContainsFuzzy(this string target, string text){
    // do the cheap stuff first
    if ( target == text ) return true;
    if ( target.Contains( text ) ) return true;
    // if the above don't return true, then do the more expensive stuff
    // such as splitting up the string or using a regex
}

那么你的LINQ代码至少会更易读:
var objects =  from x in db.Foo
               where x.Name.ContainsFuzzy("Foo McFoo")
               select x;

明显的缺点是每次调用ContainsFuzzy都需要重新创建拆分列表等,因此会涉及一些开销。您可以创建一个名为FuzzySearch的类,这样至少会提高效率:
class FuzzySearch{

    private string _searchTerm;
    private string[] _searchTerms;
    private Regex _searchPattern;

    public FuzzySearch( string searchTerm ){
        _searchTerm = searchTerm;
        _searchTerms = searchTerm.Split( new Char[] { ' ' } );
        _searchPattern = new Regex(
            "(?i)(?=.*" + String.Join(")(?=.*", _searchTerms) + ")");
    }

    public bool IsMatch( string value ){
        // do the cheap stuff first
        if ( _searchTerm == value ) return true;
        if ( value.Contains( _searchTerm ) ) return true;
        // if the above don't return true, then do the more expensive stuff
        if ( _searchPattern.IsMatch( value ) ) return true;
        // etc.
    }

}

你的 LINQ:

FuzzySearch _fuzz = new FuzzySearch( "Foo McFoo" );

var objects =  from x in db.Foo
               where _fuzz.IsMatch( x.Name )
               select x;

应该是 "(?i)(?=." + String.Join(")(?=.", _searchTerms) + ")"); - Venson

3
我想知道为什么没有人提到Levenshtein距离算法。
这是一种算法,通过一个整数告诉两个字符串之间的距离。 这里是一个SO帖子,你可以在其中找到一些此算法的实现。
因此,使用具有签名int Distance(string x, string y)的距离函数,您可以过滤高距离并对结果进行排序,使低距离出现在结果的顶部,使用LINQ。
请注意,这将导致性能开销。

1

您需要将名称提取到名字/姓氏列中,或者如果有多个别名,则可以提取到另一个表中。

但我认为您应该考虑使用类似于Lucene的东西,如果您需要一些“宽容”或“模糊”的内容

问题:是否可能编写一个搜索方法,当某人使用搜索词John Doe或Jane Doe时,返回记录一(John和Jane Doe)?

为了非常具体地回答您的问题,您可以将“John Doe”转换为LIKE '%John%Doe'或“Jane Doe”转换为LIKE '%Jane%Doe',这将检索该记录。但是,我可以看到像“Johnathan Poppadoe”这样的名称会出现问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接