Hello and welcome to lesson 5.
It's time to learn about one of the most tricky features of RavenDB: The
server-side LoadDocument
function.
In the previous lesson you wrote this code:
static void Main(string[] args)
{
using (var session = DocumentStoreHolder.Store.OpenSession())
{
var results = session
.Query<Products_ByCategory.Result, Products_ByCategory>()
.Include(x => x.Category)
.ToList();
foreach (var result in results)
{
var category = session.Load<Category>(result.Category);
Console.WriteLine($"{category.Name} has {result.Count} items.");
}
}
}
As you learned in unit 1, lesson 4 the Include
method
ensures that all related documents will be returned in a single response from the server, and
this is amazing.
As you probably deduced, RavenDB uses HTTP as the communication protocol. Here is the URL generated by the client API to execute the query of the code above:
http://localhost:8080/databases/Northwind/indexes/Products/ByCategory?&pageSize=128&include=Category
Go ahead! Click here and check out this query result.
Yes, what you get when you execute this query is a big JSON object, right? The nice thing is that you get all data you need with a single request. The bad thing is you receive a lot more than you need. In the example all related category documents will be present in the response, but you will use just one property (please, keep in mind that Category is a small document ...).
What if you could load the documents in the server-side to produce only the information you need?
When writing your index definitions you can use the LoadDocument
function to get information from related documents.
Let's rewrite the Products_ByCategory
index using the LoadDocument
function.
public class Products_ByCategory :
AbstractIndexCreationTask<Product, Products_ByCategory.Result>
{
public class Result
{
public string Category { get; set; }
public int Count { get; set; }
}
public Products_ByCategory()
{
Map = products =>
from product in products
let categoryName = LoadDocument<Category>(product.Category).Name
select new
{
Category = categoryName,
Count = 1
};
Reduce = results =>
from result in results
group result by result.Category into g
select new
{
Category = g.Key,
Count = g.Sum(x => x.Count)
};
}
}
What does it mean? Now we are no longer storing the category Id
, but the Name
. So, now we
can rewrite our program with no includes.
static void Main(string[] args)
{
using (var session = DocumentStoreHolder.Store.OpenSession())
{
var query = session
.Query<Products_ByCategory.Result, Products_ByCategory>();
//.Include(x => x.Category);
var results = (
from result in query
select result
).ToList();
foreach (var result in results)
{
//var category = session.Load<Category>(result.Category);
//Console.WriteLine($"{category.Name} has {result.Count} items.");
Console.WriteLine($"{result.Category} has {result.Count} items.");
}
}
}
We will still have only one request...
http://localhost:8080/databases/Northwind/indexes/Products/ByCategory?&pageSize=128
... but now we will have a smaller response.
The LoadDocument
feature is a really awesome one, but it is also something that should
be used carefully.
As you know, LoadDocument
allows you to load another document during indexing, and use its data in your index and that is great!
The problem with LoadDocument
is that it allows users to keep a relational
model when they work with RavenDB, and use LoadDocument
to get away with
it when they need to do something that is hard to do with RavenDB natively.
That wouldn’t be so bad, if LoadDocument
didn’t have several important costs
associated with it. For example, anytime a common referenced document is updated, an
indexing process will be triggered, and it can cost a lot for your server.
LoadDocument
is an important feature and you could and should use it eventually. But,
I strongly recommend you to treat LoadDocument
as a potential "bad smell".
Awesome! You just learned one of the most powerful and controversial features of RavenDB.
Let's move onto Lesson 6.