Tertiary includes in RavenDB

time to read 5 min | 810 words

For a while, I was absolutely against this, in fact, I refused to implement this several times when asked, because it is complicate to do and indicate design problems in your domain model.

During a course today, I found out that I might want to refuse, but the feature is actually in the product for the last two+ years, we just didn’t know about this.

But what are tertiary includes in the first place?

Let us imagine that we have something like this:

image

Now, I want to load an order, but also show its Customer and Products. Those are secondary includes, and are very easy to do with RavenDB:

var orders = session.Query<Order>()
.Include("Customer") .Include("Lines,Product") .ToList();

There is also a strongly typed option for this, of course.

What this does is instruct RavenDB to load into the session the Customer and the associated Products into the session, so when you do something like this:

var cust = session.Load<Customer>(order.Customer);

The value will be loaded from the session cache, without going to the sever. As I said, this is a feature that we have had for quite a while, and it is a really nice one, because it drastically reduce the number of queries that you have to make.

The problem is that some people want to take it one step further, they want to be able to search on an Order, but also load the Location of a Product. I don’t really like this, and as I said, when asked for this feature, I consistently said that it isn’t there because it represent a remnant of relational thinking in your design.

But as it turned out, we do support this, although quite by accident.

The reason is quite simple, we evaluate Includes only after we evaluate the TranformResults function. Which means that the TranformResults function gets to choose whatever you want to include. Here is how it works:

TranformResults  = (database, results) =>
   from result in results
   select new 
   {
     Order = result,
     Locations = result.SelectMany(x=>x.Lines).Select(x=>database.Load<Product>(x.Product).Location)
   }

And then, you can just ask to Include(“Locations”), and you are pretty much set.

Except, that this is a really awkward thing to do, and I don’t really like it at all.

Sure, I don’t like this feature, but people will use it, and if it already there, we might as well make it elegant. Therefor, we now have the option of doing:

TranformResults  = (database, results) =>
   from result in results
   let _ = database.Include(result.SelectMany(x=>x.Lines).Select(x=>x.Product))
   select result;

I think you’ll agree that this is much nicer all around, this tells the server to include the data, without us needing to explicitly ask this from the client.