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.