Indexes: Indexing Related Documents

To extend indexing capabilities and simplify many scenarios, we have introduced the possibility for indexing related documents.

Example I

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

Without this feature, you would have to create a fairly complex multiple 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,
                }
            })"
        };
    }
}

Now we will be able 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();

Example II

Our next scenario will show us how indexing of more complex relationships is also trivial. Let's consider the following case:

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 Name { get; set; }

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

    public Authors_ByNameAndBooks()
    {
        Map = authors => from author in authors
                         select new Result
                         {
                             Name = author.Name,
                             Books = 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)
                }
            })"
        };
    }
}

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

Remarks

Information

Indexes are updated automatically when related documents change.

Warning

Using the LoadDocument adds a loaded document to the tracking list. This may cause very expensive calculations to occur, especially when multiple documents are tracking the same document.