RavenDB start - client API

Posted by why not on Thu, 27 Jan 2022 03:04:13 +0100

Document session

Session is the main way for code to interact with RavenDB. The session API includes the following seven common high-level APIs:

  • Load()
  • Include()
  • Delete()
  • Query()
  • Store()
  • SaveChanges()
  • Advanced

Let's explain these seven API s separately.

Load()

We can use Load to Load one or more documents into a session, and the documents loaded into the session are managed by the session. A document can only be loaded once in a session. Let's look at the code first:

var t1 = session.Load<ToDoTask>("ToDoTasks/1-A");
var t2 = session.Load<ToDoTask>("ToDoTasks/1-A");
Assert.True(Object.ReferenceEquals(t1, t2));

In the above code, although we called session twice Load(“ToDoTasks/1-A”); , However, it only queries RavenDB once, and there is only one ToDoTask instance in the session. Whenever we load a document, we will first check whether the document exists in the dictionary within session management. If it does not exist, we will return to the existing instance. This will help to improve the system performance. Load can load multiple documents at a time. For example, three documents are loaded at a time like the following code:

Dictionary<string, ToDoTask> tasks = session.Load<ToDoTask>(
    "ToDoTasks/1-A",
    "ToDoTasks/2-A",
    "ToDoTasks/3-A"
);

In the above code, a dictionary containing all three documents will be generated, which are retrieved through one query. If the specified document is not found in RavenDB, the ID value of the document in the dictionary is null. It should be noted here that if the loaded documents are loaded, the session will return them from the session cache. If the documents do not exist, the session will remember that the document cannot be loaded, return null immediately, and will not try the document again.

Include()

In the project, we are mostly dealing with documents with associated relationships. What should we do in RavenDB? So, in this section, let's see how to deal with multiple documents. First, update our Model, add the Person entity class to the code, and modify the ToDoTask entity class:

public class Person
{
    public string Id { get; set; }
    public string Name { get; set; }
}
public class ToDoTask
{
    public string Id { get; set; }
    public string Task { get; set; }
    public bool Completed { get; set; }
    public DateTime DueDate { get; set; }

    public string AssignedTo { get; set; } 
    public string CreatedBy { get; set; } 
}

The two entity classes are independent of each other and do not refer to each other, which means that we can obtain and use a single document without loading other documents. However, we added CreatedBy and AssignedTo attributes in ToDoTask class. These two attributes represent the creator and executor of the task respectively, and their values are from the Id field in the Person class. What should we do if we want to add a ToDoTask to this Person while adding a Person? I believe some students think so:

using (var session = store.OpenSession())
{
    var person = new Person
    {
        Name = "Oscar Arava"
    };
    session.Store(person);
    session.SaveChanges();
    
    var task = new ToDoTask
    {
        DueDate = DateTime.Today.AddDays(1),
        Task = "Buy milk",
        AssignedTo = person.Id,
        CreatedBy = person.Id
    };
    session.Store(task);
    session.SaveChanges();
}

The SaveChanges method is executed twice in the code, which seems to be OK. I also mentioned in the previous article that the SaveChanges method will submit all the previously added, modified and deleted contents to RavenDB at one time, so we can delete the first SaveChanges method. At this time, another student asked, I don't save Person, call Person Don't you report an error? In fact, there is no need to worry about this problem at all. When we call session After the store (Person), RavenDB client has assigned a unique value to the Id attribute of Perosn, so it calls Person No error in Id. Now that we know how to save multiple documents, let's take a look at how to query the related documents. In RavenDB, there is actually no foreign key relationship we often say. The reference to another document is just a string attribute. So how do we query the document and its associated documents? I believe some students must think so:

using (var session = store.OpenSession())
{
    string taskId = Console.ReadLine();

    ToDoTask task = session.Load<ToDoTask>(taskId);
    Person assignedTo = session.Load<Person>(task.AssignedTo);

    Console.WriteLine(
        $"{task.Id} - {task.Task} by {assignedTo.Name}");
}

Although the above code can find out the associated data, it is inefficient. He called RavenDB twice, one to get the Task and the other to get the Pearson. This case is just a simple query, but if you want to query complex documents, this multiple calls will seriously affect the efficiency and performance, so how to solve it? In fact, the solution is also very simple. We can use the API Include(). The following code is what it looks like after modification:

using (var session = store.OpenSession())
{
    string taskId = Console.ReadLine();

    ToDoTask task = session
              .Include<ToDoTask>(x => x.AssignedTo)
              .Load(taskId);
    
    Person assignedTo = session.Load<Person>(task.AssignedTo);

    Console.WriteLine(
      $"{task.Id} - {task.Task} by {assignedTo.Name}");
}

In this code, we invoked the Include method before the Load method. This method tells RavenDB that when loading the document, it should also load the corresponding Person document according to the AssignedTo property. If assignedto has a value, a client is sent along with the ToDoTask document. At this time, when we call the load method to obtain the person document, because the document already exists in the session cache, we will not query ravendb, but directly return the data. In the same operation, we can call the Include() API multiple times. The code is as follows:

ToDoTask task = session.Include(x => x.AssignedTo)
                       .Include(x => x.CreatedBy)
                       .Load(taskId);

The above code is to ensure that if both AssignedTo and CreatedBy point to the same document, it will only return a copy of the document, no matter how many times it is referenced. However, it should be noted here that Include cannot query the referenced documents in the included documents, that is, we can query the corresponding Person documents through ToDoTask documents, but we cannot query which ToDoTask documents reference it through Person documents. The specific principle will be explained in the subsequent topics.