2012-10-31 10 views
7

Próbuję zaimplementować wyszukiwanie przeciwko DB, który odziedziczyłem. Wymóg określa, że ​​użytkownik musi mieć możliwość wyszukania obiektu według nazwy. Niestety obiekt może mieć wiele nazw skojarzonych z nim. Na przykład:Przebaczanie/Wyszukiwanie rozmyte z LINQ

ID Name 
1  John and Jane Doe 
2  Foo McFoo 
3  Boo McBoo 

to dość łatwe do wdrożenia przeszukiwanie gdy pojedyncza nazwa istnieje w każdym rekordzie:

var objects = from x in db.Foo 
       where x.Name.Contains("Foo McFoo") 
       select x; 

Jednak, gdy istnieje wiele nazw, to podejście nie działa.

Pytanie: Czy jest możliwe aby napisać metodę wyszukiwania, które rekord jednego zwrotu (John i Jane Doe), gdy ktoś korzysta z haseł John Doe lub Jane Doe?

+0

można zrobić String.split na białej przestrzeni, aby przerwać łańcuch szukania siebie i następnie po prostu uruchom wiele zapytań za pomocą.Zawiera i zwraca wszystkie wyniki? –

+0

Co, jeśli jest "John Smith"? Czy podzielisz go i szukasz każdej części nazwy? Co sprawia, że ​​imię i nazwisko? Osiągam to, że w obecnej formie nazwa nie ma żadnej struktury. – hometoast

Odpowiedz

3

To zaszkodzi wydajność, ale o tym, jak szybko jeden:

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

Wydaje powrócić czego można się spodziewać. Teraz możesz ją dostroić, by zachowywała się miło, gdy masz również płytę "John Doe" oraz "John and Jane Doe".

Czy to działa dla Ciebie?

+0

+1, to na pewno działa! Moją obawą jest wydajność ".All()". Być może jest to jedyny sposób, aby to zrobić, biorąc pod uwagę obecną konfigurację DB. Chciałbym zobaczyć reakcję społeczności na tę metodę, zanim wyzwolę na nią spust ... –

+1

Cóż, bez wszystkiego po prostu wykonalibyśmy standardowe wyszukiwanie (szybkość zależna od konfiguracji DB i wszystkiego). W przypadku rozwiązania All() mnożymy to przez 2-3 (jeśli nazwy mają na ogół jedno imię i jedno nazwisko). Więc w przypadku, gdy masz dokładny mecz, nie musiałbyś rozdzielać struny, to by zabolało. Jak zminimalizować proste wyszukiwanie i jeśli nic się nie pojawi, użyj klawisza All()? Tylko rzucanie myśli – Vladimir

0

Musisz albo przeciągnąć nazwy do kolumn First/LastName, albo do innej tabeli, być może, jeśli istnieje wiele aliasów.

Ale to, co naprawdę myślę, trzeba spojrzeć na to coś jak Lucene jeśli trzeba coś „wyrozumiały” lub „rozmyty”

Pytanie: Czy jest możliwe aby napisać metodę wyszukiwania, która wróci nagrać jedną (John and Jane Doe), gdy ktoś używa wyszukiwanych słów John Doe or Jane Doe?

Być bardzo specyficzne pytania, można konwertować "John Doe", aby LIKE '%John%Doe' lub "Jane Doe", aby LIKE '%Jane%Doe' a to odzyskać ten rekord. Jednak widziałem problemy z nazwami takimi jak "Johnathan Poppadoe".

7

Można by utworzyć niestandardową metodę rozszerzenia o nazwie "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 
} 

Wtedy twój LINQ to przynajmniej być łatwiejsze do odczytania:

var objects = from x in db.Foo 
       where x.Name.ContainsFuzzy("Foo McFoo") 
       select x; 

Oczywistą wadą jest to, że każde wywołanie ContainsFuzzy oznacza ponowne utworzenie podzielonej listy, itd., co wiąże się z pewnym obciążeniem. Można utworzyć klasę o nazwie FuzzySearch który przynajmniej nie daje pewne zwiększenie skuteczności działania:

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. 
    } 

} 

Twój LINQ:

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

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

Pomogło mi dużo, dzięki! – BjarkeCK

+0

Powinno być "(? I) (? =. *" + String.Join (") (? =. *", _searchTerms) + ")"); – JPVenson

+0

@jpv thanks - edycja wykonana. – JDB