Oren Eini

CEO of RavenDB

a NoSQL Open Source Document Database

Get in touch with me:

oren@ravendb.net +972 52-548-6969

Posts: 7,633
|
Comments: 51,252
Privacy Policy · Terms
filter by tags archive
time to read 3 min | 558 words

We will start with the Index() method:

image

There are one this in this code that bothers me, and that is that this code is going to perform two DB queries. But that is beside the point, since we are going to modify the whole thing.

And here is my port:

image

As you can see, it is pretty much the same, and not really that interesting. Let us see what else we have:

image

Something that is important to note here is that we are doing a search on the name of a genre. The problem is that the genre name isn’t the primary key, worse, there isn’t even an index on the name column. Now, admittedly, the genre table contains ten rows, but it is the principal of the thing. (If you are smart, you only have to be read the riot act by the DBA about non index queries in production once).

Now, it would be trivial for us to implement this in Raven using the same approach, but I don’t see a reason to do this. The genre that we get in the Browse method is dependant on the data that we return from the Index method, so there is no reason no to pass the id of the genre directly. I modified the Index() action to pass the entire genre, not just the genre name, and to pass the id back to the Browse() action, not the name.

I gotten started implementing this, but I got stuck on the association of Albums from the genre.

image

Document database doesn’t normally have associations, and they don’t have joins. So how can we do this?

By now, you should be pretty familiar with the answer, we need to define an index :-)

// AlbumsByGenre
from album in docs.Albums
where album.Genre != null
select new { Genre = album.Genre.Id }

And this index allows us to write this code:

image

And finally, we have the GenreMenu:

image

Which we can port very easily:

image

And that is all for the StoreController

time to read 3 min | 548 words

I noticed that I had  typo when I inserted the albums data, the artist data was stored as “Arist”. This give me a chance to show you how we can do a migration that is a bit more advanced.

using (var documentStore = new DocumentStore { Url = "http://localhost:8080" })
{
    documentStore.Initialise();

    var count = 0;

    do
    {
        var queryResult = documentStore.DatabaseCommands.Query("Raven/DocumentsByEntityName", new IndexQuery
        {
            Query = "Tag:`Albums`",
            PageSize = 128,
            Start = count
        });


        if (queryResult.Results.Length == 0)
            break;

        count += queryResult.Results.Length;
        var cmds = new List<ICommandData>();
        foreach (var result in queryResult.Results)
        {
            var arist = result.Value<JObject>("Arist");
            if(arist == null)
                continue;
                        
            result["Artist"] = arist;
            result.Remove("Arist");

            cmds.Add(new PutCommandData
            {
                Document = result,
                Metadata = result.Value<JObject>("@metadata"),
                Key = result.Value<JObject>("@metadata").Value<string>("@id"),
            });
        }

        documentStore.DatabaseCommands.Batch(cmds.ToArray());

    } while (true);
    
}

The code itself should be hard to follow I think, it shows how we can manipulate documents by working with the JSON document directly, instead of having to go through an object layer.

time to read 1 min | 98 words

I am ashamed to say how much time and effort (and at least one complete re-design) this bug has cost me.

Take a look at the following test, meant to show that Raven can replicate deletes between servers:

image

There is a very subtle bug in this code, which completely tripped me.

Here is a hint, the session interface is the high level client API for Raven.

Can you spot the bug?

time to read 5 min | 807 words

On my last post, I mention that we need to add a CountSold property to all the albums, in most SQL system, something like that can be pretty painful. The syntax for adding a new column is easy, but actually getting it done, and deployed, and versioned, is pretty hard. With Raven, if you add a new property, it will automatically be added to your document when you next save it. There is no action required on your part. The same, by the way, would happen when you remove a property. Raven will clean it up after you.

The question is what happens when we want to set that value to something, not just to the default value? We need to provide that logic somehow, and here is a simple way of doing so;

using (var documentStore = new DocumentStore { Url = "http://localhost:8080" })
{
    documentStore.Initialise();
    using (var session = documentStore.OpenSession())
    {
        IDictionary<string,int> albumToSoldCount = new Dictionary<string, int>();
        int count = 0;

        do
        {
            var results = session.Query<SoldAlbum>("SoldAlbums")
                .Take(128)
                .Skip(count)
                .ToArray();

            if (results.Length == 0)
                break;
            count += results.Length;
            foreach (var soldAlbum in results)
            {
                albumToSoldCount[soldAlbum.Album] = soldAlbum.Quantity;
            }
        } while (true);

        count = 0;
        do
        {
            var albums = session.Query<Album>()
                .Skip(count)
                .Take(128)
                .ToArray();
            if (albums.Length == 0)
                break;

            foreach (var album in albums)
            {
                int value;
                albumToSoldCount.TryGetValue(album.Id, out value);

                album.CountSold = value;
            }

            count += albums.Length;

            session.SaveChanges();
            session.Clear();
        } while (true);
    }
}

To those of you who haven’t bother to read the code, this is reading the index that we previously created and remembering its value. Then we start reading batches of albums and update their counts. All in all, it is quite simple.

An additional nice property of this script is that you can run it is safe to run it multiple times.

time to read 2 min | 350 words

As I mentioned, we can solve the GetTopSellingAlbums() problem using map/reduce, but that isn’t really a good way of doing it. The problem with doing that (aside from the scared looks and pained sounds that you get when you mention it) is that it is trying to solve the problem in a relational way. Indeed, the previous solution was an near duplication of how a relational database would process that query. So, what is the doc db approach for solving this issue?

The answer is quit simple, remember that documents are independent, and think about the question. What we are asking is what are the top selling albums. If we add a CountSold property to the album, we would suddenly find it so much easier to handle this problem. This means that we would need to update all the albums that are part of a given order when an order is submitted, but that is acceptable (this exact same operation is commonly done in SQL databases as well).

For now, let us waive how we create the CountSold property and fill it with the right values (I’ll discuss it in my next post), for now, assume that this happened, how can we GetTopSellingAlbums() problem?

Well, that is easy enough. All we need to do is define an index for CountSold.

// AlbumsByCountSold
from album in docs.Albums
select new { album.CountSold };

With that, we can implement GetTopSellingAlbums like this:

image

And now it is done, very simple, very efficient and quit elegant, eve if I say so myself.

time to read 4 min | 621 words

The current HomeController looks like this:

image

I really don’t like the fact that the controller issues queries like that, but we will let it go for now.

This query (thanks to EF Prof) looks like this:

image

And here we run into a very interesting problem, we can’t really replicate this query. The reason is that this query runs over multiple tables which our model says would be in different documents.

There are several ways in which we can fix this. One way of doing this would be to define a map / reduce index on top of orders.

Note: Yes, I am familiar with this comic.

The way that I am about to show you isn’t the way I would recommend going for real, but I want to show it anyway. I’ll discuss the idiomatic Raven way of handling this feature in my next post.

Map/reduce in Raven is just a couple of Linq queries, so it is nothing to be worried about. As a reminder, we have the following order documents in our database:

image_thumb9 image image

We define the index “SoldAlbums” using the following queries.

// map
from order in docs.Orders
from line in order.Lines
select new{ line.Album, line.Quantity }

// reduce
from result in results
group result by result.Album into g
select new{ Album = g.Key, Quantity = g.Sum(x=>x.Quantity) }

As you can see, those are two very simple Linq queries.

The result of which would be:

image

Once we have that, it is trivial to derive the answer to GetTopSellingAlbums. Indeed, the following function implements the exact same logic and has the same output as the previous implementation:

image

The way it work is pretty simple, we get the most sold albums (by sorting on descending quantity), then load them from the database. Because we might have less than count top selling albums, we need to top it off from regular albums.

This mean that this code execute 2 – 3 queries. I don’t really like it, but on my machine, it takes about less than 10 ms to do all three requests, which is livable.

The reason that I am posting this solution is that I want to show this as an approach to a problem, not as the recommended approach for how to solve it, I’ll do that in my next post.

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. API Design (10):
    29 Jan 2026 - Don't try to guess
  2. Recording (20):
    05 Dec 2025 - Build AI that understands your business
  3. Webinar (8):
    16 Sep 2025 - Building AI Agents in RavenDB
  4. RavenDB 7.1 (7):
    11 Jul 2025 - The Gen AI release
  5. Production postmorterm (2):
    11 Jun 2025 - The rookie server's untimely promotion
View all series

Syndication

Main feed ... ...
Comments feed   ... ...
}