2012-03-15 26 views
10

que tiene un conjunto de objetos de tipo Ideabúsqueda de subcadenas en RavenDB

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

Quiero buscar estos objetos por la subcadena. Por ejemplo, cuando tengo el objeto del título "idea", quiero que se encuentre cuando ingrese cualquier subcadena de "idea": i, id, ide, idea, d, de, dea, e, ea , a.

Estoy usando RavenDB para almacenar datos. La consulta de búsqueda se ve así:

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

mientras que el índice es el siguiente:

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() es un método de extensión que devuelve todas distintas subseries de determinada cadena:

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

Este es no funciona. Particularmente porque RavenDB no reconoce el método SplitSubstrings(), porque está en mi ensamblaje personalizado. ¿Cómo hacer que esto funcione, básicamente cómo forzar a RavenDB a reconocer este método? Además de eso, ¿es mi enfoque apropiado para este tipo de búsqueda (búsqueda por subcadena)?

EDITAR

Básicamente, quiero construir función de autocompletar en esta búsqueda, por lo que necesita para ser rápido.

enter image description here

Por cierto: estoy usando RavenDB - Build # 960

+0

Los índices de RavenDB se ejecutan en el servidor y, por lo tanto, no tienen acceso a un código personalizado como ese. El índice que escribe se convierte en una cadena, se envía al servidor y se compila allí, el código StringExtension no va con él, de ahí el error. –

+0

Sé que esto es responsabilidad del lado del servidor, pero ¿hay alguna forma de insertar allí mi código personalizado? Tal vez usando el reflejo? – jwaliszko

Respuesta

9

Puede realizar la búsqueda de subcadenas en varios campos utilizando el enfoque siguiente:

(1)

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

en this site que pueda compruebe que:

"De forma predeterminada, RavenDB usa un analizador personalizado llamado LowerCaseKeywordAnalyzer para todo el contenido. (...) Los valores predeterminados para cada campo son FieldStorage.No en Stores y FieldIndexing.Default en Indexes."

Así por defecto, si usted comprueba los términos de índice en el interior del cliente cuervo, se ve siguiente:

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

Basado en esto, consulta comodín se pueden construir:

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

que luego se usa con las construcciones .In y .Where (usando el operador OR en el interior):

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

(2)

Como alternativa, se puede usar puro consulta lucene:

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

(3)

también puede utilizar .Search expresión, pero usted tiene que construir su índice de forma diferente si desea buscar en varios campos:

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

Resumen:

también tienen en cuenta que *term* es bastante caro, especialmente el comodín líder. En este post puede encontrar más información al respecto. Se dice que ese comodín líder obliga a lucene a realizar un escaneo completo en el índice y, por lo tanto, puede ralentizar drásticamente el rendimiento de la consulta. Lucene almacena internamente sus índices (en realidad los términos de los campos de cadenas) ordenados alfabéticamente y "lee" de izquierda a derecha. Esa es la razón por la que es rápido hacer una búsqueda de un comodín final y lento para un líder.

Así, alternativamente x.Title.StartsWith("something") se puede utilizar, pero esto obviamente no buscar en todas las subcadenas. Si necesita una búsqueda rápida, puede cambiar la opción Índice para que los campos que desea buscar se analicen, pero de nuevo no buscará en todas las subcadenas.

Si hay una barra espaciadora interior de la consulta subcadena, por favor marque esta question para una posible solución. Para hacer sugerencias, marque http://architects.dzone.com/articles/how-do-suggestions-ravendb.

0

logré hacer esto en la memoria con el siguiente código:

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

Esto me salvó el problema de ir en la forma RavenDB de buscar cadenas con el método Contiene como se describe en Daniel Lang's post.

El método de extensión Contains es la siguiente:

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

El problema con esto es que está retirando TODOS los documentos del cliente de RavenDB y luego filtrándolos en la memoria (como usted señala). Esto podría funcionar con algunos documentos, pero cuando tenga 100 o incluso 1000 va a tener que comenzar a buscar a través de ellos y el rendimiento no será excelente. –

+0

Por lo tanto, aunque el método descrito en la publicación de Daniel podría ser un poco más de trabajo, la mejora es mejor porque hace todo el trabajo en el servidor y luego solo envía los documentos coincidentes. –

+0

@MattWarren: Sin duda, amigo mío. Consideré esta implicación al escribir el código, pero estoy satisfecho con el rendimiento actual. Tal vez cambie esto en el futuro. Olvidé mencionar que estoy usando este código para crear una funcionalidad de autocompletar. Por cierto, la publicación de Daniel realmente no muestra cómo imitar la funcionalidad Contiene ya que usa solo StartWith y EndWith. –

2

Esto parece ser un duplicado de RavenDB fast substring search

La respuesta allí, que no se menciona aquí, es el uso de un analizador de Lucene personalizada llamada Ngram

+0

hola, es bueno saber sobre NGram, en realidad esta pregunta se hizo antes que la otra, pero ambas abordan el mismo tema – jwaliszko

+0

Didn capte las fechas. Estás en lo correcto. :) –

1

En caso que cualquier otra persona se encuentra con esto. Raven 3 tiene un método de extensión Search() que permite la búsqueda de subcadenas.

Un par de trampas:

  • Prestar especial atención a la sección "Consulta de escape" en la parte inferior
  • Yo no lo vi mencionado en cualquier lugar, pero sólo funcionaba para mí si fue el Search() se añade directamente a Query() (es decir, sin ningún tipo de Where(), OrderBy(), etc entre ellos)

Hope esto ahorra a alguien algo de frustración.

Cuestiones relacionadas