2012-03-15 5 views
10

J'ai un ensemble d'objets de type Idearecherche Substring dans RavenDB

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

Je veux chercher ce objets par sous-chaîne. Par exemple quand j'ai l'objet du titre "idée", je veux qu'il soit trouvé quand j'entre toute sous-chaîne de "idée": je, id, ide, idée, d, de, dea, e, ea un. J'utilise RavenDB pour stocker des données. La requête de recherche ressemble que:

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

alors que l'indice suit:

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() est une méthode d'extension qui retourne sous-chaînes toutes distinctes de chaîne donnée:

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(); 
    } 
} 

C'est ne fonctionne pas. Particulièrement parce que RavenDB ne reconnaît pas la méthode SplitSubstrings(), parce qu'elle est dans mon assemblage personnalisé. Comment faire ce travail, essentiellement comment forcer RavenDB à reconnaître cette méthode? De plus, est-ce que mon approche est appropriée pour ce genre de recherche (recherche par sous-chaîne)?

EDIT

Fondamentalement, je veux construire fonctionnalité de remplissage automatique sur cette recherche, il faut être rapide.

enter image description here

BTW: J'utilise RavenDB - Build # 960

+0

Les index RavenDB s'exécutent sur le serveur et n'ont donc pas accès à du code personnalisé comme celui-ci. L'index que vous écrivez est transformé en chaîne, envoyé au serveur et compilé là-bas, le code StringExtension ne va pas avec, d'où l'erreur. –

+0

Je sais que c'est la responsabilité côté serveur, mais est-il possible d'y injecter mon code personnalisé? Peut-être en utilisant la réflexion? – jwaliszko

Répondre

9

Vous pouvez effectuer une recherche de sous-chaîne sur plusieurs champs en utilisant l'approche suivante:

(1)

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

sur this site vous pouvez vérifier que:

"Par défaut, RavenDB utilise un analyseur personnalisé appelé LowerCaseKeywordAnalyzer pour tout le contenu. (...) Les valeurs par défaut pour chaque champ sont FieldStorage.No dans Stores et FieldIndexing.Default dans Index."

Donc, par défaut, si vous vérifiez les termes d'index à l'intérieur du client corbeau, il semble suivant:

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

Sur cette base, peut être construit requête générique:

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

qui est ensuite utilisé avec les constructions .In et .Where (en utilisant l'opérateur OU à l'intérieur):

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

(2)

Vous pouvez également utiliser la requête pure Lucene:

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

(3)

Vous pouvez également utiliser l'expression .Search, mais vous devez construire votre index différemment si vous souhaitez effectuer une recherche sur plusieurs champs:

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>(); 

Résumé:

ont à l'esprit que *term* est assez cher, surtout le premier caractère générique. Dans ce post vous pouvez trouver plus d'informations à ce sujet. Il est dit, que le premier caractère générique oblige Lucene à faire un balayage complet sur l'index et peut ainsi ralentir considérablement la performance de la requête. Lucene stocke ses index en interne (en fait les termes des champs de chaînes) classés par ordre alphabétique et "lit" de gauche à droite. C'est la raison pour laquelle il est rapide de faire une recherche pour un caractère générique final et de ralentir pour un caractère générique.

Donc, alternativement x.Title.StartsWith("something") peut être utilisé, mais cela ne fait évidemment pas de recherche dans toutes les sous-chaînes. Si vous avez besoin d'une recherche rapide, vous pouvez modifier l'option Index pour que les champs que vous souhaitez rechercher soient analysés, mais cela ne fera pas de recherche dans toutes les sous-chaînes.

S'il y a une barre d'espace intérieur de la sous-chaîne requête, s'il vous plaît vérifier question pour une solution possible. Pour faire des suggestions, vérifiez http://architects.dzone.com/articles/how-do-suggestions-ravendb.

0

J'ai réussi à le faire en mémoire avec le code suivant:

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); 
} 

Cela m'a sauvé la difficulté d'aller RavenDB façon de rechercher des chaînes avec la méthode Contient comme décrit par Daniel Lang's post.

La méthode d'extension Contains est la suivante:

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

Le problème avec ceci est que vous retirez TOUS les documents client de RavenDB et ensuite les filtrez en mémoire (comme vous le faites remarquer). Cela peut fonctionner avec quelques docs, mais quand vous avez 100 ou même 1000, vous devrez commencer à pagayer à travers eux et le perf ne sera pas génial. –

+0

Alors que la méthode décrite dans la publication de Daniel pourrait être un peu de travail supplémentaire, la perf est meilleure car elle fait tout le travail sur le serveur et ensuite seulement renvoyer les documents correspondants. –

+0

@MattWarren: Sans doute mon ami. J'ai considéré cette implication lors de l'écriture du code mais je suis satisfait de la perf actuelle. Peut-être que je vais changer cela à l'avenir. J'ai oublié de mentionner que j'utilise ce code pour créer une fonctionnalité d'auto-complétion. À propos, le post de Daniel ne montre pas vraiment comment imiter la fonctionnalité Contient puisqu'il n'utilise que StartWith et EndWith. –

2

Cela semble être un double de RavenDB fast substring search

La réponse là-bas, qui n'a pas été mentionné ici, est d'utiliser un analyseur personnalisée Lucene appelé NGram

+0

Salut, bon à savoir sur NGram, en fait cette question a été posée avant l'autre, mais toujours les deux abordent le même sujet – jwaliszko

+0

Didn pas attraper les dates. Vous avez raison. :) –

1

Incase quelqu'un d'autre vient à travers cela. Raven 3 a une méthode d'extension Search() qui permet la recherche de sous-chaînes.

Quelques gotchas:

  • Portez une attention particulière à la « Requête échapper » section au bas
  • Je ne l'ai pas mentionné nulle part, mais il ne fonctionnait pour moi si Search() était la ajouté directement à Query() (ie sans Where(), OrderBy(), etc entre eux)

espère que cela va quelqu'un une certaine frustration.