19/02/2012

RavenDB (cz. 4) - zapytania 2

Home

Ten post to kontynuacja poprzedniego postu dotyczącego zapytań w Raven DB.

Contains == Equals !!!

Jedną z funkcjonalności jaką chciałem zaimplementować w swoim programie było wyszukiwanie wyrażeń/tłumaczeń zawierających podany ciąg znaków. Brzmi prosto ale nie obyło się bez problemów. Otóż okazało się, że Raven DB traktuje wywołanie metody String.Contains jako String.Equals, chyba że do poszukiwanego ciągu znaków dodamy gwiazdki. Na przykład zamiast s.Contains("kot") użyjemy s.Contains("kot*"), s.Contains("*kot") lub s.Contains("*kot*") w zależności czy poszukiwane wyrażenie ma znajdować się na początku, na końcu lub w środku.
string textToFind = String.Format("*{0}*", textToFind);

var res = 
 from ex in session.Query<ExpressionEntity>()
 where ex.Expression!= null && ex.Expression.Contains(textToFind) || ex.Translations.Any(t => t.Translation != null && t.Translation.Contains(textToFind))
 orderby ex.Expression
 select ex
Pozostaje jeden problem, którego niestety nie udało mi się rozwiązać. Ten kod nie zadziała jeśli szukany tekst zawiera białe znaki. Załóżmy, że na liście naszych wyrażenie mamy wyrażenie Ala ma kota. Jeśli spróbujemy wyszukać "*Ala*", "*ma*", "*kota*" otrzymamy poprawny wynik. Jeśli natomiast spróbujemy wyszukać całe wyrażenie "*Ala ma kota*" zapytanie zwróci nam pustą listę. Co dziwniejsze jeśli nie użyjemy gwiazdek czyli zlecimy wyszukanie "Ala ma kota" wyrażenie zostanie znalezione.

Ale co, jeśli szukam podwyrażenia "Ala ma" lub "ma kota"? Nie jest to duże pocieszenie ale Raven DB poradzi sobie z "Ala ma*" (z "*ma kota" już nie). Moim zdaniem sugeruje to, że wewnętrznie Raven DB do wyszukiwania używa jakiejś struktury opartej o prefixy.

Małe ostrzeżenie

W jednym z zapytań chciałem użyć metody String.Equals ale zakończyło się to wyjątkiem NullReferenceException. Sądzę, że jest to związany z użyciem metod statycznych bo kiedy użyłem metody instancyjnej np.: ex.Expression.Equals(textToFind, StringComparison.InvariantCultureIgnoreCase) obyło się bez błędów.

Stale Data

Jedną z głównych idei przyświecających twórcom Raven DB było zapewnić aby pytający o dane jak najszybciej uzyskał odpowiedź nawet jeśli oznaczałoby to zwrócenie nieaktualnych, przestarzałych danych. Używając Raven DB trzeba być na to przygotowanym nawet pracując z małą ilością danych. W moim przypadku baza danych zawiera ok. 600 dokumentów, nie całe 0.5 MB po wyeksportowaniu do pliku XML i obserwuję ten efekt.

Na przykład na początku moja aplikacja wyświetlała liczbę wyrażeń obok etykiety z nazwą języka. Jeśli jednak użytkownik importował do aplikacji wiele wyrażeń to operacja zapisywania danych, odświeżania indeksów itd. po stronie bazy danych trwała na tyle długo, że wyliczona liczba wyrażeń nie zgadzała się ze stanem faktycznym.
  • Użytkownik rozpoczyna import wyrażeń.
  • Zakończenie importu z perspektywy aplikacji/użytkownika.
  • Obliczenie liczby wyrażeń w poszczególnych językach.
  • ...
  • Faktyczne zakończenie importu po stronie bazy danych.
W tej chwili do problemu podchodzę tak, że liczbę słów w danym języku wyświetlam na życzenie. Jeśli takie zachowanie Raven DB jednak nam przeszkadza to należy explicite powiedzieć, że nie śpieszy się nam i możemy poczekać na aktualne dane np.:

var res = 
 from ex in session.Query<ExpressionEntity>().Customize(a => a.WaitForNonStaleResults())
 select ex;

Lucene

Raven DB jako silnika indeksujący używa technologii Lucene .NET. Co ciekawe umożliwia zdefiniowanie zapytań w składni jaką posługuje się ten silnik. Bezpośrednie użycie Lucene może wyglądać tak:
var res = session.Advanced.LuceneQuery<ExpressionEntity>().Where(luceneQuery);
Gdzie luceneQuery to po prostu ciąg znaków zawierający zapytanie w składni zrozumiałej dla Lucene. Może to się przydać kiedy będziemy chcieli zadać zapytanie nie obsługiwane przez "LINQ to Raven DB".

Indeksy

Jeszcze parę słów o indeksach. Termin ten przewinął sie już kilka razy. Pracując z Raven DB trzeba sobie przede wszystkim uświadomić, że tutaj indeksy różnią się od indeksów używanych w bazach relacyjnych. Moim zdaniem dwie różnice są fundamentalne. Po pierwsze Raven DB indeksuje zapytania, a nie dane. Indeks definiujemy definiując zapytanie, a nie wskazując na przykład atrybut dokumentu, który ma zostać zaindeksowany.

Po drugie Raven DB używa indeksów przy wykonywaniu każdego zapytania nawet jeśli taki indeks nie został zdefiniowany. W takiej sytuacji tworzony jest indeks dynamiczny. Druga grupa indeksów to indeksy statyczne, zdefiniowane explicite przez użytkownika na podstawie jego wiedzy o zadawanych pytaniach. Tymczasowe indeksy dynamiczne mogą zostać zamienione na stałe jeśli Raven DB zauważy, że taki indeks jest często używany.

Podsumowanie

O ile sposób realizacji podstawowych operacji dodaj/usuń/zmodyfikuj na dokumentach bardzo chwaliłem ze względu na jego prostotę i niski koszt wejścia to o raportowaniu/zapytaniach nie mogę już tego powiedzieć. Z jednej strony nie jest to żaden rocket science ale z drugiej nie jest to już "bułka z masłem" i wymaga znajomości rożnych sztuczek i specyfiki Raven DB. Jeśli uwzględnić, że zapytania jakich użyłem były bardzo proste, a pomimo to sprawiły mi sporo kłopotów to obawiam się, chociaż są to tylko moje przypuszczenia, że realizacja skomplikowanych raportów może być w Raven DB bardzo nietrywialna.

Podsumujmy co już umiemy:
  • Osadzić Raven DB w aplikacji hostującej.
  • Zainicjować Raven DB.
  • Skonfigurować dostęp do Raven Studio i API REST'owego.
  • Tworzyć obiekty POCO jakie mogą zostać umieszczone w Raven DB.
  • Dodawać/usuwać/modyfikować dokumenty.
  • Zadawać proste i te trochę bardziej skomplikowane zapytania.
  • Utworzyć indeks.
  • Skorzystać z algorytmu Map/Reduce.
  • Skorzystać z zapytań Lucene.
  • Wymusić zwrócenie przez zapytanie aktualnych danych.

1 comments:

Unknown said...

odnośnie wyszukiwania: Contains == Equals !!!

http://ravendb.net/docs/client-api/querying/static-indexes/configuring-index-options

tutaj znajduje się wyjaśnienie dlaczego to tak działa. jednym słowem musisz utworzyć index z odpowiednim analizatorem składni.

Post a Comment