We have been loading documents since lesson 3, and now it's time to learn what alternatives you have.
The session is the primary way your code interacts with RavenDB. You need to create a session via the document store, and then use the session methods to perform operations.
A session object is very cheap to create and very easy to use. To create a
session, you simply call the DocumentStore.OpenSession()
method.
using (var session = DocumentStoreHolder.Store.OpenSession())
{
// ready to interact with the database
}
As the name implies, the Load
method gives you the option of loading a document
or a set of documents, passing the document(s) ID(s) as parameters. The result will
be an object representing the document or null
if the document does not exist.
A document is loaded only once in a session. Even though we call the Load
method
twice passing the same document ID, only a single remote call to the server will
be made. Whenever a document is loaded, it is added to an internal dictionary managed
by the session.
This exercise picks up right where the previous one in the last lesson left off.
Change your program to call the Load
method two times passing the same
value as the parameter. Then, use the Debug.Assert
method to confirm the
resulting objects are the same.
using (var session = DocumentStoreHolder.Store.OpenSession())
{
var p1 = session.Load<Product>("products/1-A");
var p2 = session.Load<Product>("products/1-A");
Debug.Assert(ReferenceEquals(p1, p2));
}
Load can also read more than a single document at a time. For example:
var products = session.Load<Product>(new [] {
"products/1-A",
"products/2-A",
"products/3-A"
});
This will result in a dictionary with three documents in it, retrieved in a single remote call from the server.
The easiest way to kill your application performance is to make a lot of remote calls. RavenDB provides a lot of features to help you significantly reduce calls and boost performance.
Consider the Northwind products/1-A
document:
{
"Name": "Chai",
"Supplier": "suppliers/1-A",
"Category": "categories/1-A",
"QuantityPerUnit": "10 boxes x 20 bags",
"PricePerUnit": 18,
"UnitsInStock": 1,
"UnitsOnOrder": 39,
"Discontinued": false,
"ReorderLevel": 10,
"@metadata": {
"@collection": "Products"
}
}
As you can see, the Supplier
and Category
properties are clearly references to
other documents.
Considering you need to load the product and the related category, how would you write the code? Your first attempt could be something like this:
var p = session.Load<Product>("products/1-A");
var c = session.Load<Category>(p.Category);
This approach will make two remote calls -- not good.
var p = session
.Include<Product>(x => x.Category)
.Load("products/1-A");
var c = session.Load<Category>(p.Category);
The Include
session method changes the way RavenDB will process the request.
It will:
- Find a document with the ID:
products/1-A
- Read its
Category
property value - Find a document with that ID
- Send both documents back to the client
When the session.Load<Category>(p.Category);
is executed, the document is in the
session cache and no additional remote call is made.
Here is a powerful example of the application of this technique in a complex scenario.
var order = session
.Include<Order>(x => x.Company)
.Include(x => x.Employee)
.Include(x => x.Lines.Select(l => l.Product))
.Load("orders/1-A");
This code will, in a single remote call, load the order, include the company and employee documents, and also load all the products in all the lines in the order.
In this exercise we will create an "Orders Explorer" for the Northwind database.
As you learned in lesson 2, start Visual Studio and create a new Console Application Project
named
Northwind. Then, in the Package Manager Console
, issue the following command:
Install-Package RavenDB.Client -Version 5.4.5
Then you will need to add the using
namespace at the top of Program.cs
:
using Raven.Client.Documents;
As you learned in lesson 3, add a new class in your project named DocumentStoreHolder
.
Then replace the file content with:
using System;
using Raven.Client;
using Raven.Client.Documents;
namespace OrdersExplorer
{
public static class DocumentStoreHolder
{
private static readonly Lazy<IDocumentStore> LazyStore =
new Lazy<IDocumentStore>(() =>
{
var store = new DocumentStore
{
Urls = new[] { "http://localhost:8080" },
Database = "Northwind"
};
return store.Initialize();
});
public static IDocumentStore Store =>
LazyStore.Value;
}
}
As you learned in lesson 2, you can work only with dynamic objects. It is a good idea to have compiler/IDE support in your projects.
Add a new class in your project named NorthwindModels. Then replace the file content with this.
Back to Program.cs
, let's create a minimal user interface which requests order numbers.
using static System.Console;
using Raven.Client.Documents;
using NorthwindModels;
namespace OrdersExplorer
{
class Program
{
static void Main(string[] args)
{
while (true)
{
WriteLine("Please, enter an order # (0 to exit): ");
int orderNumber;
if (!int.TryParse(ReadLine(), out orderNumber))
{
WriteLine("Order # is invalid.");
continue;
}
if (orderNumber == 0) break;
PrintOrder(orderNumber);
}
WriteLine("Goodbye!");
}
private static void PrintOrder(int orderNumber)
{
}
}
}
Now it's time to load order data, but only if there is an order with the specified order number.
private static void PrintOrder(int orderNumber)
{
using (var session = DocumentStoreHolder.Store.OpenSession())
{
var order = session
.Include<Order>(o => o.Company)
.Include(o => o.Employee)
.Include(o => o.Lines.Select(l => l.Product))
.Load($"orders/{orderNumber}-A");
if (order == null)
{
WriteLine($"Order #{orderNumber} not found.");
return;
}
WriteLine($"Order #{orderNumber}");
var c = session.Load<Company>(order.Company);
WriteLine($"Company : {c.Id} - {c.Name}");
var e = session.Load<Employee>(order.Employee);
WriteLine($"Employee: {e.Id} - {e.LastName}, {e.FirstName}");
foreach (var orderLine in order.Lines)
{
var p = session.Load<Product>(orderLine.Product);
WriteLine($" - {orderLine.ProductName}," +
$" {orderLine.Quantity} x {p.QuantityPerUnit}");
}
}
}
Let's move on to Lesson 6 and learn about querying, now in C#.