This is the second post in a small series about RavenDB – a document database in .NET. I will try to touch the same areas as I did in my series on MongoDB, possibly comparing the two where I see fit.
Now, this time we will take a look at querying…
Introduction
Querying with MongoDB is extremely easy and intuitive when you’re used to relational databases – just type in some queries in the form of JSON documents and send them to the server and let it do its work.
Querying with RavenDB is a different story, because the only thing that can be queried is an index[1. Or is it? I should point out that Ayende said that RavenDB would have dynamically generated temporary indexes in the future, allowing ad-hoc quering… but what’s more, those temporary indexes would “materialize” and become permanent if you hit them enough times… that actually sounds extremely cool, and should allow for some truly frictionless and agile-feeling development.]. Let’s see…
Simple query
Let’s start out by handing a couple of documents to the server. Let’s execute the following:
1 2 3 4 5 6 7 8 |
using (var session = documentStore.OpenSession()) { session.Store(new Movie {Title = "The Big Lebowski", ViewCount = 200}); session.Store(new Movie {Title = "Fear And Loathing In Las Vegas", ViewCount = 100}); session.Store(new Movie {Title = "Adaptation", ViewCount = 20}); session.SaveChanges(); } |
Now, in order to be able to query these documents, we need to tell RavenDB to create an index… I like to be code-driven when I can, so let’s do it with C#… first, make a class somewhere derived from AbstractIndexCreationTask:
1 2 3 4 5 6 7 8 9 10 11 |
public class MoviesByViewCount : AbstractIndexCreationTask { public override IndexDefinition CreateIndexDefinition() { return new IndexDefinition<Movie> { Map = movies => from movie in movies select new {movie.ViewCount} }.ToIndexDefinition(DocumentStore.Conventions); } } |
In this example, I build the index purely from mapping the collection – i.e. there’s no reduce step. Note that the map step is a LINQ query that maps each document into the fields that should be used to build the index. That means that the example above will allow me to query this index and constrain by ViewCount of each movie.
Next, upon initialization, we need to tell RavenDB to create our indexes (if they have not already been created – otherwise, they’ll be updated):
1 2 3 4 |
var documentStore = new DocumentStore{...}; documentStore.Initialize(); IndexCreation.CreateIndexes(typeof (MoviesByViewCount).Assembly, documentStore); |
That was easy. Now, let’s take the index for a spin… first let’s just get everything [1. Note that because of RavenDB’s excellent safe-by-default philosophy, at most 128 documents will be returned! Therefore, the usual .Skip(n) and .Take(m) methods should be used to properly page the result sets.
Note also, that by default you can perform only 30 operations resulting in remote calls within one IDocumentSession. This is another constraint that will guide you away from blowing off that left foot of yours :)]:
1 2 3 4 5 6 7 8 9 10 |
using(var session = documentStore.OpenSession()) { var movies = from m in session.Query<Movie>(typeof (MoviesByViewCount).Name) select m; foreach(var movie in movies) { Console.WriteLine("Got {0} ({1} views)", movie.Title, movie.ViewCount); } } |
which results in the following output in the console:
1 2 3 4 5 |
Executing query '' on index 'MoviesByViewCount' in 'http://localhost:8080' Query returned 3/3 results Got The Big Lebowski (200 views) Got Fear And Loathing In Las Vegas (100 views) Got Adaptation (20 views) |
which is pretty much what we expected. Note however that RavenDB is nice enough to tell when a query is executed and how many results are returned.
Now let’s use our index and change the query into this:
1 2 3 |
var movies = from m in session.Query<Movie>(typeof (MoviesByViewCount).Name) where m.ViewCount <= 100 select m; |
which gives me the following output:
1 2 3 4 |
Executing query 'ViewCount_Range:[* TO 0x00000064]' on index 'MoviesByViewCount' in 'http://localhost:8080' Query returned 2/2 results Got Fear And Loathing In Las Vegas (100 views) Got Adaptation (20 views) |
Note how the query criteria are translated into a Lucene query – that’s because RavenDB uses Lucene. NET for all of its indexing work. Just for the fun of it, let’s try using the index to constrain by title:
1 2 3 |
var movies = from m in session.Query<Movie>(typeof (MoviesByViewCount).Name) where m.Title == "The Big Lebowski" select m; |
which results in the following output:
1 2 |
Executing query 'Title:[["The Big Lebowski"]]' on index 'MoviesByViewCount' in 'http://localhost:8080' Query returned 0/0 results |
It appears RavenDB will just go ahead and query Lucene for it, even though the index doesn’t have the specified field. Kind of weird, but maybe it’s because Lucene is itself a document DB, and there’s no way to tell beforehand whether a given index contains a document with the specified field.
Now that was a couple of simple queries. Next time, let’s try building a map/reduce query!
“Now, in order to be able to query these documents, we need to tell RavenDB to create an index… ”
That’s not actually true either, it is recommended that you use the relatively new feature to just use
session.Query().Where(x=>x.SomeProperty == "Something" // etc).ToArray()
Which will create the index for you in the background 🙂
Yeah, that feature is pretty cool! I mention that in the first footnote whose reference, I admit, is way too hard to see 🙂 I will correct that.
Wait – so you’re saying that LINQ querying is the recommended way of creating the index?
I can see why this is a nice feature for simple queries, but won’t you get in trouble with some more advanced queries?