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.