Indexes: Indexing Related Documents


Important

The indexes will be updated automatically whenever related documents change.

Linking Many Documents to a Constantly Changing Document

LoadDocument can be a useful way to enable rapid querying of related documents by having an index do the work behind the scenes.

However, it has a performance cost if you frequently modify documents that are referenced by many other documents. Referencing frequently changed documents will repeatedly trigger background indexing on every related document. This can tax system resources and cause slow, stale indexing.
Consider using Include( ) instead.

Example I

Let's consider a simple Product - Category scenario where you want to look for a Product by Category Name.

Product-Category Link in JSON Documents

Product-Category Link in JSON Documents

Without LoadDocument, you would have to create a fairly complex Multi-Map-Reduce index.
This is why the LoadDocument function was introduced.

public class Products_ByCategoryName : AbstractIndexCreationTask<Product>
{
    public class Result
    {
        public string CategoryName { get; set; }
    }

    public Products_ByCategoryName()
    {
        Map = products => from product in products
                          select new Result
                          {
                              CategoryName = LoadDocument<Category>(product.Category).Name
                          };
    }
}
store.Maintenance.Send(new PutIndexesOperation
(
    new IndexDefinition
    {
        Name = "Products/ByCategoryName",
        Maps =
        {
            @"from product in products
            select new
            {
                CategoryName = LoadDocument(product.Category, ""Categories"").Name
            }"
        }
    }
));
public class Products_ByCategoryName : AbstractJavaScriptIndexCreationTask
{
    public class Result
    {
        public string CategoryName { get; set; }
    }

    public Products_ByCategoryName()
    {
        Maps = new HashSet<string>()
        {
            @"map('products', function(product ){
                return {
                    CategoryName : load(product .Category, 'Categories').Name,
                }
            })"
        };
    }
}
docs.Products.Select(product =>
new{CategoryName = (this.LoadDocument(
    product.Category, "Categories")).Name
    })

To see how this code works with Northwind sample data, you can create sample data for a playground server or see the Code Walkthrough.
Now we can query the index to search for products using the CategoryName as a parameter:

IList<Product> results = session
    .Query<Products_ByCategoryName.Result, Products_ByCategoryName>()
    .Where(x => x.CategoryName == "Beverages")
    .OfType<Product>()
    .ToList();
IList<Product> results = await asyncSession
    .Query<Products_ByCategoryName.Result, Products_ByCategoryName>()
    .Where(x => x.CategoryName == "Beverages")
    .OfType<Product>()
    .ToListAsync();
// Note that the naming separator character "_" in the index definition name becomes "/" in RQL
from index "Products/ByCategoryName"
where CategoryName == "Beverages"

Example II

Our next scenario will show us how indexing more complex relationships is also straightforward.
Let's consider the following case where we'll want to query two related documents:

public class Book
{
    public string Id { get; set; }

    public string Name { get; set; }
}

public class Author
{
    public string Id { get; set; }

    public string Name { get; set; }

    public IList<string> BookIds { get; set; }
}

To create an index with Author Name and list of Book Names, we need do the following:

public class Authors_ByNameAndBooks : AbstractIndexCreationTask<Author>
{
    public class Result
    {
        public string AuthorName { get; set; }

        public IEnumerable<string> BookNames { get; set; }
    }

    public Authors_ByNameAndBooks()
    {
        Map = authors => from author in authors
                         select new Result
                         {
                             AuthorName = author.Name,
                             BookNames = author.BookIds.Select(x => LoadDocument<Book>(x).Name)
                         };
    }
}
store.Maintenance.Send(new PutIndexesOperation
(
    new IndexDefinition
    {
        Name = "Authors/ByNameAndBooks",
        Maps =
        {
            @"from author in docs.Authors
            select new
            {
                Name = author.Name,
                Books = author.BookIds.Select(x => LoadDocument(x, ""Books"").Id)
            }"
        }
    }
));
public class Authors_ByNameAndBookNames : AbstractJavaScriptIndexCreationTask
{
    public class Result
    {
        public string Name { get; set; }

        public IList<string> Books { get; set; }
    }

    public Authors_ByNameAndBookNames()
    {
        Maps = new HashSet<string>()
        {
            @"map('Author', function(a){
                return {
                    Name: a.Name,
                    Books: a.BooksIds.forEach(x => load(x, 'Book').Name)
                }
            })"
        };
    }
}

We can now query the index by specifying fields from related documents.

IList<Author> results = session
    .Query<Authors_ByNameAndBooks.Result, Authors_ByNameAndBooks>()
    .Where(x => x.AuthorName == "Andrzej Sapkowski" || x.BookNames.Contains("The Witcher"))
    .OfType<Author>()
    .ToList();