2012-03-15 7 views
10

Ho una serie di oggetti di tipo Idearicerca Substring in RavenDB

public class Idea 
{ 
    public string Title { get; set; } 
    public string Body { get; set; } 
} 

voglio cercare questo oggetti da stringa. Per esempio quando ho l'oggetto del titolo "idea", voglio che venga trovato quando inserisco una sottostringa di "idea": i, id, ide, idea, d, de, dea, e, ea , a.

Sto utilizzando RavenDB per la memorizzazione dei dati. La query di ricerca sembra che:

var ideas = session 
       .Query<IdeaByBodyOrTitle.IdeaSearchResult, IdeaByBodyOrTitle>() 
       .Where(x => x.Query.Contains(query)) 
       .As<Idea>() 
       .ToList(); 

mentre l'indice è la seguente:

public class IdeaByBodyOrTitle : AbstractIndexCreationTask<Idea, IdeaByBodyOrTitle.IdeaSearchResult> 
{ 
    public class IdeaSearchResult 
    { 
     public string Query; 
     public Idea Idea; 
    } 

    public IdeaByBodyOrTitle() 
    { 
     Map = ideas => from idea in ideas 
         select new 
          { 
           Query = new object[] { idea.Title.SplitSubstrings().Concat(idea.Body.SplitSubstrings()).Distinct().ToArray() }, 
           idea 
          }; 
     Indexes.Add(x => x.Query, FieldIndexing.Analyzed); 
    } 
} 

SplitSubstrings() è un metodo di estensione che restituisce sottostringhe tutto distinte di data stringa:

static class StringExtensions 
{ 
    public static string[] SplitSubstrings(this string s) 
    { 
     s = s ?? string.Empty; 
     List<string> substrings = new List<string>(); 
     for (int i = 0; i < s.Length; i++) 
     {     
      for (int j = 1; j <= s.Length - i; j++) 
      { 
       substrings.Add(s.Substring(i, j)); 
      } 
     }    
     return substrings.Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToArray(); 
    } 
} 

Questo è non funziona. Soprattutto perché RavenDB non riconosce il metodo SplitSubstrings(), perché è nel mio assembly personalizzato. Come fare questo lavoro, in pratica come forzare RavenDB a riconoscere questo metodo? Oltre a ciò, il mio approccio è appropriato per questo tipo di ricerca (ricerca per sottostringa)?

EDIT

Fondamentalmente, voglio costruire funzione di completamento automatico su questa ricerca, quindi hanno bisogno di essere veloce.

enter image description here

Btw: sto usando RavenDB - Costruire # 960

+0

Gli indici RavenDB vengono eseguiti sul server e quindi non hanno accesso a un codice personalizzato del genere. L'indice che scrivi viene trasformato in una stringa, inviato al server e compilato lì, il codice StringExtension non lo accompagna, quindi l'errore. –

+0

So che questa è una responsabilità lato server, ma esiste un modo per inserire il mio codice personalizzato? Forse usando la riflessione? – jwaliszko

risposta

9

È possibile eseguire la ricerca sottostringa su più campi utilizzando seguente approccio:

(1)

public class IdeaByBodyOrTitle : AbstractIndexCreationTask<Idea> 
{ 
    public IdeaByBodyOrTitle() 
    { 
     Map = ideas => from idea in ideas 
         select new 
          { 
           idea.Title, 
           idea.Body 
          }; 
    } 
} 

su this site si può verificare che:

"Per impostazione predefinita, RavenDB utilizza un analizzatore personalizzato denominato LowerCaseKeywordAnalyzer per tutto il contenuto. (...) I valori predefiniti per ogni campo sono FieldStorage.No in Stores e FieldIndexing.Default negli indici ."

Quindi per impostazione predefinita, se si controlla i termini di indice all'interno del client di corvo, sembra seguente:

Title     Body 
------------------  ----------------- 
"the idea title 1"  "the idea body 1" 
"the idea title 2"  "the idea body 2" 

Sulla base di ciò, interrogare jolly possono essere costruiti:

var wildquery = string.Format("*{0}*", QueryParser.Escape(query)); 

che viene quindi utilizzato con le costruzioni .In e .Where (utilizzando l'operatore OR all'interno):

var ideas = session.Query<User, UsersByDistinctiveMarks>() 
        .Where(x => x.Title.In(wildquery) || x.Body.In(wildquery)); 

(2)

In alternativa, è possibile utilizzare query di Lucene puro:

var ideas = session.Advanced.LuceneQuery<Idea, IdeaByBodyOrTitle>() 
        .Where("(Title:" + wildquery + " OR Body:" + wildquery + ")"); 

(3)

È anche possibile utilizzare .Search espressione, ma si deve costruire l'indice in modo diverso se si desidera effettuare la ricerca in più campi:

public class IdeaByBodyOrTitle : AbstractIndexCreationTask<Idea, IdeaByBodyOrTitle.IdeaSearchResult> 
{ 
    public class IdeaSearchResult 
    { 
     public string Query; 
     public Idea Idea; 
    } 

    public IdeaByBodyOrTitle() 
    { 
     Map = ideas => from idea in ideas 
         select new 
          { 
           Query = new object[] { idea.Title, idea.Body }, 
           idea 
          }; 
    } 
} 

var result = session.Query<IdeaByBodyOrTitle.IdeaSearchResult, IdeaByBodyOrTitle>() 
        .Search(x => x.Query, wildquery, 
          escapeQueryOptions: scapeQueryOptions.AllowAllWildcards, 
          options: SearchOptions.And) 
        .As<Idea>(); 

sintesi:

hanno anche in mente che *term* è piuttosto costoso, soprattutto il carattere jolly leader. In questo post puoi trovare maggiori informazioni a riguardo. Si dice, che le forze wildcard principali lucene effettuino una scansione completa dell'indice e possano quindi drasticamente rallentare le prestazioni delle query. Lucene memorizza internamente i suoi indici (in realtà i termini dei campi stringa) ordinati alfabeticamente e "legge" da sinistra a destra. Questo è il motivo per cui è veloce fare una ricerca per un carattere jolly finale e lento per uno leader.

Così in alternativa x.Title.StartsWith("something") può essere utilizzato, ma questo ovviamente non ricerca contemporanea in tutte le sottostringhe. Se avete bisogno di ricerca rapida, è possibile modificare l'opzione di indice per i campi che si desidera per cercare di essere analizzato, ma ancora una volta non sarà la ricerca in tutti sottostringhe.

Se c'è una barra spaziatrice all'interno della query sottostringa, si prega di controllare questo question per una possibile soluzione. per fare suggerimenti controllare http://architects.dzone.com/articles/how-do-suggestions-ravendb.

0

sono riuscito a fare questo in memoria con il seguente codice:

public virtual ActionResult Search(string term) 
{ 
    var clientNames = from customer in DocumentSession.Query<Customer>() 
         select new { label = customer.FullName }; 

    var results = from name in clientNames.ToArray() 
        where name.label.Contains(term, 
              StringComparison.CurrentCultureIgnoreCase) 
        select name; 

    return Json(results.ToArray(), JsonRequestBehavior.AllowGet); 
} 

Questo mi ha salvato il problema di andare in modalità RavenDB alla ricerca di stringhe con il metodo Contains come descritto da Daniel Lang's post.

Il metodo di estensione Contains è questo:

public static bool Contains(this string source, string toCheck, StringComparison comp) 
{ 
    return source.IndexOf(toCheck, comp) >= 0; 
} 
+0

Il problema con questo è che stai ritirando TUTTI i documenti del cliente da RavenDB e poi li filtra in memoria (come fai notare). Questo potrebbe funzionare con alcuni documenti, ma quando hai 100 o anche 1000 devi iniziare a sfogliarli e il perf non sarà grande. –

+0

Quindi, anche se il metodo descritto nel post di Daniel potrebbe essere un po 'più di lavoro, il perf è migliore perché sta facendo tutto il lavoro sul server e poi invia solo i documenti corrispondenti. –

+0

@MattWarren: Senza dubbio amico mio. Ho considerato questa implicazione quando ho scritto il codice, ma sono soddisfatto dell'attuale perf. Forse cambierò questo in futuro. Ho dimenticato di dire che sto usando questo codice per creare una funzionalità di completamento automatico. A proposito, il post di Daniel non mostra in realtà come imitare la funzionalità Contiene dal momento che utilizza solo StartWith e EndWith. –

2

Questo sembra essere un duplicato di RavenDB fast substring search

La risposta c'è, che non è stato menzionato qui, è quello di utilizzare un analizzatore di Lucene personalizzato chiamato Ngram

+0

ciao, buono a sapersi su NGram, in realtà questa domanda è stata posta prima dell'altra, ma entrambe trattiamo lo stesso argomento – jwaliszko

+0

Didn prendi le date Hai ragione. :) –

1

Incase chiunque altro si imbatte in questo. Raven 3 ha un metodo Search() estensione che permette di stringa di ricerca.

Un paio di grattacapi:

  • prestare particolare attenzione alla sezione "Query fuga" in fondo
  • Non ho visto che menzionato da nessuna parte, ma ha funzionato solo per me se Search() era la aggiunto direttamente al Query() (cioè senza alcun Where(), OrderBy(), ecc tra di loro)

Spero che questo consente di risparmiare qualcuno qualche frustrazione.