2012-10-31 18 views
7

Sto tentando di implementare una ricerca su un DB che ho ereditato. Il requisito stabilisce che l'utente deve essere in grado di cercare un oggetto per nome. Sfortunatamente, a un oggetto potrebbero essere associati più nomi. Per esempio:Perdita/ricerca fuzzy con LINQ

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

E 'abbastanza facile da implementare una ricerca quando esiste un solo nome in ogni record:

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

Tuttavia, quando esistono più nomi, questo approccio non funziona.

Domanda: E 'possibile scrivere un metodo di ricerca che sarebbe tornato un record (John e Jane Doe) quando qualcuno usa i termini di ricerca o John DoeJane Doe?

+0

si potrebbe fare uno string.split su uno spazio bianco per rompere la stringa di ricerca a parte e quindi esegui più query con.Contiene e restituisce tutti i risultati? –

+0

Cosa succede se c'è un "John Smith"? Lo dividi e cerchi ogni parte del nome? Cosa rende un nome e un cognome? Quello che sto ottenendo è che nella sua forma attuale il nome non ha struttura. – hometoast

risposta

3

Questo avrebbe fatto male le prestazioni, ma come su questo svelta:

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

sembra di restituire ciò che ci si aspetterebbe. Ora lo sintonizzeresti per comportarti bene quando hai anche un disco "John Doe" e "John and Jane Doe".

Funziona per voi?

+0

+1, sicuramente funziona! La mia preoccupazione è la prestazione '.All()'. Forse questo è l'unico modo per farlo dato l'attuale configurazione del DB. Mi piacerebbe vedere la reazione della community a questo metodo prima di premere il grilletto su di esso ... –

+1

Beh, senza tutto quello che vorresti fare una ricerca standard (velocità dipendente dalla configurazione di DB e da tutti). Con la soluzione All(), si moltiplica quella con 2-3 in media (se i nomi hanno generalmente un FirstName e un LastName). Quindi, nel caso in cui si abbia una corrispondenza esatta, non si dovrebbe dividere la stringa, ciò farebbe male. Che ne dici di mitigare con una semplice ricerca all'inizio e se non compare nulla, usa il bit All()? Ho appena lanciato pensieri – Vladimir

0

È necessario estrarre i nomi nelle colonne First/LastName o in un'altra tabella, se esistono più alias.

Ma quello che penso davvero che si dovrebbe guardare qualcosa come Lucene se avete bisogno di qualcosa di 'perdonare' o 'sfocata'

Domanda: E 'possibile scrivere un metodo di ricerca che sarebbe tornato registrare uno (John e Jane Doe) quando qualcuno usa i termini di ricerca John Doe o Jane Doe?

Per essere molto specifico alla tua domanda, è possibile convertire "John Doe" per LIKE '%John%Doe' o "Jane Doe" per LIKE '%Jane%Doe' e questo sarebbe recuperare quel record. Tuttavia potrei vedere problemi con nomi come "Johnathan Poppadoe".

7

si potrebbe creare un metodo di estensione personalizzata denominata "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 
} 

Allora il vostro LINQ sarebbe almeno più facile da leggere:

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

Lo svantaggio evidente è che ogni chiamata a ContainsFuzzy significa ricreare la tua lista divisa, ecc., quindi c'è un po 'di spese generali. Si potrebbe creare una classe chiamata FuzzySearch quali almeno dovrebbe dare qualche maggiore effeciency:

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

} 

tuo LINQ:

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

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

Mi ha aiutato molto, grazie! – BjarkeCK

+0

Deve essere "(? I) (? =. *" + String.Join (") (? =. *", _searchTerms) + ")"); – JPVenson

+0

@jpv grazie - modifica apportata. – JDB