07/02/2012

RavenDB (cz. 3) - zapytania

Home

Wstęp

W tej części serii poświęconej Raven DB napiszę o zapytaniach, pobieraniu danych. Temat sam w sobie jest bardzo obszerny i to, co napiszę, to tylko szczyt góry lodowej. Większość tematów po prostu zasygnalizuje, ale sądzę, że dobrze pokaże, jak to wygląda z Raven DB.

Podstawy

Podstawowe zapytania zadajemy w bardzo prosty sposób i robimy to otwierając najpierw sesję pracy z bazą np.:
using (var session = Store.OpenSession())
{
 var res = from ex in session.Query<ExpressionEntity>()
    select ex;
 ...
}
Aby pobrać listę dokumentów użyłem metody Query określając jaki typ dokumentów mnie interesuje. Dla przypomnienia klasa ExpressionEntity reprezentuje wyrażenie i jego tłumaczenia. Jest to ta sama klasa, która wcześniej posłużyła mi do umieszczenia danych w bazie.

Jeszcze jeden przykład. Tym razem pobieram listę dokumentów by na jej podstawie określić listę różnych kategorii, jakie zdefiniowano dla wszystkich wyrażeń.
var res = (from e in session.Query<ExpressionEntity>()
    where e.Category != null
    orderby e.Category
    select e.Category).Distinct();
Jak widać podstawowe zapytania wykonuje się bardzo łatwo.

Stronicowanie

Przy dużej liczbie dokumentów przydatne okaże się stronicowanie. To też nie jest trudne do zrealizowania:
var res = (from ex in session.Query<ExpressionEntity>()
    orderby ex.Expression
    select ex).Skip(index * pageSize).Take(pageSize).ToList();
Użyta w kodzie zmienna index to indeks strony do pobrania (w numerowaniu od zera), a pageSize to oczywiście wielkość strony liczona w liczbie dokumentów. Metoda Skip pozwala więc na przeskoczenie do konkretnej strony, a metoda Take na pobranie takiej liczby dokumentów jaka mieści się na stronie.

Robi się trudniej

Jedną z funkcjonalności, jakie chciałem mieć w swoim programie LanguageTrainer, było zliczanie liczby wyrażeń posiadających tłumaczenie w danym języku. Brzmi prosto, prawda? Pierwsza moja próba wyglądała tak:
 var count = 
 (from ex in session.Query<ExpressionEntity>()
 from t in ex.Translations
 where t.Language == selectedLang && !String.IsNullOrEmpty(t.Translation) 
 select 1).Count();
Zmienna selectedLang zawiera interesujący nas język. Wykonanie takiego zapytania zakończy się wyjątkiem NotSupportedException z komunikatem Method not supported: SelectMany. A więc może coś takiego:
var count =
 (from ex in session.Query<ExpressionEntity>()
 where ex.Translations.Any(t => t.Language == selectedLang && !String.IsNullOrEmpty(t.Translation))
 select 1).Count();
Tym razem zakończy się wyjątkiem z komunikatem Method not supported: IsNullOrEmpty. No cóż IsNullOrEmpty łatwo zastapić zwykłym porównaniem. Spróbujmy więc jeszcze raz:
var count = 
 (from ex in session.Query<ExpressionEntity>()
 where ex.Translations.Any(t => t.Language == selectedLang && t.Translation != null && t.Translation != String.Empty)
 select 1).Count();
To też nie zadziała i znowu zakończy sie błędem, tym razem z komunikatem Node not supported: Constant. Jeszcze jednak próba i w końcu zadziałało:
var count = 
 (from ex in session.Query<ExpressionEntity>()
 where ex.Translations.Any(t => t.Language == selectedLang && t.Translation != null && t.Translation != String.Empty)
 select ex).Count();
Nie jest to skomplikowane ale wymaga znajomości kilku "trików", nie jest do końca intuicyjne.

MapReduce

Do opisanego powyżej problemu można też podejście w Raven DB w inny sposób, a mianowicie stosując algorytm MapReduce. W ten sposób wykonując jedno zapytanie otrzymamy wyniki dla wszystkich języków za jednym razem. W Raven DB robimy to definiując indeks (jeszcze o tym napiszę):
public class TranslationsCounter : AbstractIndexCreationTask<ExpressionEntity, TranslationsCounter.ReduceResult>
{
 public class ReduceResult
 {
  public Languages Lang { get; set; }
  public int Count { get; set; }
 }

 public TranslationsCounter()
 {
  Map = docs => from doc in docs
   from t in doc.Translations
   select new { Lang = t.Language, Count = String.IsNullOrEmpty(t.Translation) ? 0 : 1 };

  Reduce = results => from t in results
   group t by t.Lang
   into g
   select new { Lang = g.Key, Count = g.Sum(x => x.Count) };
 }
}
i zadanie zapytania przy jego użyciu np.:
public IDictionary<Languages,int> CountExpressionsByLanguage()
{
 using (var session = Store.OpenSession())
 {
  var dict = new Dictionary<Languages, int>();

  foreach(var res in session.Query<TranslationsCounter.ReduceResult, TranslationsCounter>())
  {
   dict.Add(res.Lang, res.Count);
  }

  return dict;
 }
}


To jeszcze nie koniec. Do tematu wrócę w kolejnym poście.

0 comments:

Post a Comment