2012-03-15 17 views
10

Mam zestaw obiektów typu Ideawyszukiwania podciągu w RavenDB

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

chcę szukać obiektów przez ten podciąg. Na przykład, gdy mam obiekt o tytule "pomysł", chcę go znaleźć po wprowadzeniu dowolnego podciągu "idea": i, id, ide, idea, d, de, dea, e, ea ,.

Używam RavenDB do przechowywania danych. Wyszukiwane wygląda tak:

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

podczas gdy indeks jest następujący:

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() stanowi metodę rozszerzenia, które zwraca wszystkie odrębne podciągi z danym wyrażenie:

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

To jest nie działa. Szczególnie, że RavenDB nie rozpoznaje metody SplitSubstrings(), ponieważ jest w moim niestandardowym zestawie. Jak to działa, w zasadzie jak zmusić RavenDB do rozpoznania tej metody? Poza tym, czy moje podejście jest odpowiednie dla tego rodzaju poszukiwań (wyszukiwanie przez podłańcuch)?

EDIT

Zasadniczo, chcę zbudować funkcję autouzupełniania w tym poszukiwaniu, więc to musi być szybka.

enter image description here

Btw: Używam RavenDB - Budowanie # 960

+0

Indeksy RavenDB działają na serwerze, więc nie mają dostępu do niestandardowego kodu w ten sposób. Indeks, który piszesz zostaje przekształcony w ciąg znaków, wysłany na serwer i skompilowany tam, kod StringExtension nie pasuje do niego, stąd błąd. –

+0

Wiem, że to jest odpowiedzialność po stronie serwera, ale czy istnieje jakiś sposób, aby wprowadzić tam mój niestandardowy kod? Może używając refleksji? – jwaliszko

Odpowiedz

9

Można wykonać podciągu wyszukiwanie w wielu dziedzinach przy użyciu następujące podejście:

(1)

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

na this site możesz sprawdź, czy:

"Domyślnie RavenDB używa niestandardowego analizatora o nazwie LowerCaseKeywordAnalyzer dla wszystkich treści. (...) Domyślne wartości dla każdego pola to FieldStorage.No w sklepach i FieldIndexing.Default w Indeksy."

Więc domyślnie, jeśli sprawdzić warunki indeksu wewnątrz kruk klienta, wygląda następujące:

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

Na tej podstawie kwerendy wieloznacznych można skonstruować:

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

który jest następnie używany z konstrukcjami .In i .Where (przy użyciu operatora OR w środku):

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

(2)

Alternatywnie, można użyć czystego kwerendy Lucene:

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

(3)

Można również użyć .Search wyrażenie, ale trzeba budować Twój indeks inaczej, jeśli chcesz przeszukać wiele pól:

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

Podsumowanie:

mieć także na uwadze, że *term* jest dość drogie, zwłaszcza prowadząc wieloznaczny. W tym post można znaleźć więcej informacji na jego temat. Mówi się, że wiodąca karta wieloznaczna zmusza lucene do wykonania pełnego skanowania indeksu, co może drastycznie spowolnić wydajność zapytań. Lucene wewnętrznie przechowuje swoje indeksy (właściwie terminy pól łańcuchowych) posortowane alfabetycznie i "czyta" od lewej do prawej. To jest powód, dla którego szybkie jest wyszukiwanie ciągłego znaku wieloznacznego i powolne dla wiodącego.

Można alternatywnie użyć x.Title.StartsWith("something"), ale oczywiście nie należy przeszukiwać wszystkich podciągów. Jeśli potrzebujesz szybkiego wyszukiwania, możesz zmienić opcję indeksu dla pól, które chcesz przeszukać, aby były analizowane, ale znowu nie przeszukuje wszystkich podciągów.

Jeśli istnieje spacji wewnątrz kwerendy podciągu, prosimy o zaznaczenie tego question do ewentualnego rozwiązania. Aby dokonać sugestii, sprawdź http://architects.dzone.com/articles/how-do-suggestions-ravendb.

0

udało mi się zrobić to w pamięci z następującego kodu:

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

To uratowało mnie Problem polegający na szukaniu w RavenDB metod wyszukiwania ciągów metodą Contains opisaną przez Daniel Lang's post.

Contains metodę rozszerzenia to:

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

Problem polega na tym, że wycofujesz WSZYSTKIE dokumenty klienta z RavenDB, a następnie filtrujesz je w pamięci (jak wskazujesz). Może to działać z kilkoma dokumentami, ale kiedy masz już 100 lub nawet 1000, będziesz musiał rozpocząć wywoływanie przez nie, a perfek nie będzie świetny. –

+0

Podczas gdy metoda opisana w poście Daniela może wymagać trochę dodatkowej pracy, perf jest lepszy, ponieważ wykonuje całą pracę na serwerze, a następnie wysyła tylko odpowiadające dokumenty. –

+0

@MattWarren: Bez wątpienia mój przyjaciel. Zastanowiłem się nad tym przy pisaniu kodu, ale jestem zadowolony z obecnego perf. Może zmienię to w przyszłości. Zapomniałem wspomnieć, że używam tego kodu, aby utworzyć funkcję automatycznego uzupełniania. Przy okazji, posta Daniela tak naprawdę nie pokazuje jak naśladować funkcjonalność Contains, ponieważ używa tylko StartWith i EndWith. –

2

To wydaje się być kopią RavenDB fast substring search

tamtejszych odpowiedź, która nie została tu wspomniano, jest użycie niestandardowego analizatora Lucene nazwie Ngram

+0

cześć, dobrze wiedzieć o NGram, faktycznie to pytanie zostało zadane przed drugim, ale wciąż oba odnoszą się do tego samego tematu – jwaliszko

+0

Didn złapać daty. Masz rację. :) –

1

Okrywać ktoś natknie to. Raven 3 ma metodę rozszerzenia Search(), która pozwala na wyszukiwanie podłańcuchowe.

Kilka pułapek:

  • zwrócić szczególną uwagę do sekcji „Zapytanie uciekającego” na dole
  • nie widzę go nigdzie wspomniane, ale pracował tylko dla mnie, czy był Search() dodaje się bezpośrednio do Query() (czyli bez Where(), OrderBy() itd między nimi)

Nadziei Oszczędza to ktoś jakąś frustrację.