Indexes: Map Indexes

Map indexes, sometimes referred to as simple indexes, contain one (or more) mapping functions that indicate which fields from the documents should be indexed. They indicate which documents can be searched by which fields.

These mapping functions are LINQ-based functions or JavaScript function (when using JavaScript indexes) and can be considered the core of indexes.

What Can be Indexed

You can:

Indexing Single Fields

Let's create an index that will help us search for Employees by their FirstName, LastName, or both.

  • First, let's create an index called Employees/ByFirstAndLastName

    Note: The naming separator character "_" in your code will become "/" in the index name.
    In the following sample, "Employees_ByFirstAndLastName" will become "Employees/ByFirstAndLastName" in your indexes list.

public class Employees_ByFirstAndLastName : AbstractIndexCreationTask<Employee>
{
    // ...
}
public class Employees_ByFirstAndLastName : AbstractJavaScriptIndexCreationTask
{
    // ...
}

You might notice that we're passing Employee as a generic parameter to AbstractIndexCreationTask. This gives our indexing function a strongly-typed syntax. If you are not familiar with AbstractIndexCreationTask, you can read this article before proceeding.

  • The next step is to create the indexing function itself. This is done by setting the Map property with our function in a parameterless constructor.

public Employees_ByFirstAndLastName()
{
    Map = employees => from employee in employees
                       select new
                       {
                           FirstName = employee.FirstName,
                           LastName = employee.LastName
                       };
}
public Employees_ByFirstAndLastName()
{
    Map = employees => employees
        .Select(employee => new
        {
            FirstName = employee.FirstName,
            LastName = employee.LastName
        });
}
public Employees_ByFirstAndLastName()
{
    Maps = new HashSet<string>
    {
        @"map('Employees', function (employee){ 
                return { 
                    FirstName : employee.FirstName, 
                    LastName : employee.LastName
                };
            })",
    };
}
  • The final step is to deploy it to the server and issue a query using the session Query method:

IList<Employee> employees1 = session
    .Query<Employee, Employees_ByFirstAndLastName>()
    .Where(x => x.FirstName == "Robert")
    .ToList();

IList<Employee> employees2 = session
    .Query<Employee>("Employees/ByFirstAndLastName")
    .Where(x => x.FirstName == "Robert")
    .ToList();
from index 'Employees/ByFirstAndLastName'
where FirstName = 'Robert'

Our final index looks like:

public class Employees_ByFirstAndLastName : AbstractIndexCreationTask<Employee>
{
    public Employees_ByFirstAndLastName()
    {
        Map = employees => from employee in employees
                           select new
                           {
                               FirstName = employee.FirstName,
                               LastName = employee.LastName
                           };
    }
}
public class Employees_ByFirstAndLastName : AbstractJavaScriptIndexCreationTask
{
    public Employees_ByFirstAndLastName()
    {
        Maps = new HashSet<string>
        {
            @"map('Employees', function (employee){ 
                    return { 
                        FirstName : employee.FirstName, 
                        LastName : employee.LastName
                    };
                })",
        };
    }
}

Field Types

Please note that indexing capabilities are detected automatically from the returned field type from the indexing function.

For example, if our Employee will have a property called Age that is an integer then the following indexing function...

from employee in docs.Employees
select new
{
	Age = employee.Age
}
map('Employees', function(employee)
{
    return {
        Age : employee.Age
    };
})

...grants us the capability to issue numeric queries (return all the Employees whose Age is more than 30).

Changing the Age type to a string will take that capability away from you. The easiest example would be to issue .ToString() on the Age field...

from employee in docs.Employees
select new
{
	Age = employee.Age.ToString()
}
map('Employees', function(employee)
{
    return {
        Age : employee.Age.toString()
    };
})

Convention

You will probably notice that in the Studio, this function is a bit different from the one defined in the Employees_ByFirstAndLastName class:

from employee in docs.Employees
select new
{
	FirstName = employee.FirstName,
	LastName = employee.LastName
}

The part you should pay attention to is docs.Employees. This syntax indicates from which collection a server should take the documents for indexing. In our case, documents will be taken from the Employees collection. To change the collection, you need to change Employees to the desired collection name or remove it and leave only docs to index all documents.

Combining Multiple Fields Together

Since each index contains a LINQ function, you can combine multiple fields into one.

Example I

Index definition:

public class Employees_ByFullName : AbstractIndexCreationTask<Employee>
{
    public class Result
    {
        public string FullName { get; set; }
    }

    public Employees_ByFullName()
    {
        Map = employees => from employee in employees
                           select new Result
                           {
                               FullName = employee.FirstName + " " + employee.LastName
                           };
    }
}
public class Employees_ByFullName : AbstractJavaScriptIndexCreationTask
{
    public class Result
    {
        public string FullName { get; set; }
    }

    public Employees_ByFullName()
    {
        Maps = new HashSet<string>
        {
            @"map('Employees', function (employee){ 
                    return { 
                        FullName  : employee.FirstName + ' ' + employee.LastName
                    };
                })",
        };
    }
}

Query the index:

// notice that we're 'cheating' here
// by marking result type in 'Query' as 'Employees_ByFullName.Result' to get strongly-typed syntax
// and changing type using 'OfType' before sending query to server
IList<Employee> employees = session
    .Query<Employees_ByFullName.Result, Employees_ByFullName>()
    .Where(x => x.FullName == "Robert King")
    .OfType<Employee>()
    .ToList();
IList<Employee> employees = session
    .Advanced
    .DocumentQuery<Employee, Employees_ByFullName>()
    .WhereEquals("FullName", "Robert King")
    .ToList();
from index 'Employees/ByFullName'
where FullName = 'Robert King'

Example II

Information

In this example, the index field Query combines all values from various Employee fields into one. The default Analyzer on fields is changed to enable Full Text Search operations. The matches no longer need to be exact.

You can read more about analyzers and Full Text Search here.

Index definition:

public class Employees_Query : AbstractIndexCreationTask<Employee>
{
    public class Result
    {
        public string[] Query { get; set; }
    }

    public Employees_Query()
    {
        Map = employees => from employee in employees
                           select new Result
                           {
                               Query = new[]
                                {
                                    employee.FirstName,
                                    employee.LastName,
                                    employee.Title,
                                    employee.Address.City
                                }
                           };

        Index("Query", FieldIndexing.Search);
    }
}
public class Employees_Query : AbstractJavaScriptIndexCreationTask
{
    public class Result
    {
        public string[] Query { get; set; }
    }

    public Employees_Query()
    {
        Maps = new HashSet<string>
        {
            @"map('Employees', function (employee) { 
                    return { 
                        Query : [employee.FirstName, 
                                 employee.LastName,
                                 employee.Title,
                                 employee.Address.City] 
                            } 
                    })"
        };
        Fields = new Dictionary<string, IndexFieldOptions>()
        {
            {"Query", new IndexFieldOptions(){ Indexing = FieldIndexing.Search} }
        };
    }
}

Query the index:

IList<Employee> employees = session
    .Query<Employees_Query.Result, Employees_Query>()
    .Search(x => x.Query, "John Doe")
    .OfType<Employee>()
    .ToList();
IList<Employee> employees = session
    .Advanced
    .DocumentQuery<Employees_Query.Result, Employees_Query>()
    .Search(x => x.Query, "John Doe")
    .SelectFields<Employee>()
    .ToList();
from index 'Employees/Query'
where search(Query, 'John Doe')

Indexing Partial Field Data

Imagine that you would like to return all employees that were born in a specific year. You can do it by indexing Birthday from Employee in the following way:

Index definition:

public class Employees_ByBirthday : AbstractIndexCreationTask<Employee>
{
    public class Result
    {
        public DateTime Birthday { get; set; }
    }

    public Employees_ByBirthday()
    {
        Map = employees => from employee in employees
                           select new Result
                           {
                               Birthday = employee.Birthday
                           };
    }
}
public class Employees_ByBirthday : AbstractJavaScriptIndexCreationTask
{
    public class Result
    {
        public DateTime Birthday { get; set; }
    }

    public Employees_ByBirthday()
    {
        Maps = new HashSet<string>
        {
            @"map('Employees', function (employee){ 
                        return {
                            Birthday : employee.Birthday 
                        } 
                   })"
        };
    }
}

Query the index:

DateTime startDate = new DateTime(1963, 1, 1);
DateTime endDate = startDate.AddYears(1).AddMilliseconds(-1);
IList<Employee> employees = session
    .Query<Employees_ByBirthday.Result, Employees_ByBirthday>()
    .Where(x => x.Birthday >= startDate && x.Birthday <= endDate)
    .OfType<Employee>()
    .ToList();
DateTime startDate = new DateTime(1963, 1, 1);
DateTime endDate = startDate.AddYears(1).AddMilliseconds(-1);
IList<Employee> employees = session
    .Advanced
    .DocumentQuery<Employees_ByBirthday.Result, Employees_ByBirthday>()
    .WhereBetween(x => x.Birthday, startDate, endDate)
    .OfType<Employee>()
    .ToList();
from index 'Employees/ByBirthday '
where Birthday between '1963-01-01' and '1963-12-31T23:59:59.9990000'

RavenDB gives you the ability to extract field data and to index by it. A different way to achieve our goal will look as follows:

Index definition:

public class Employees_ByYearOfBirth : AbstractIndexCreationTask<Employee>
{
    public class Result
    {
        public int YearOfBirth { get; set; }
    }

    public Employees_ByYearOfBirth()
    {
        Map = employees => from employee in employees
                           select new Result
                           {
                               YearOfBirth = employee.Birthday.Year
                           };
    }
}
public class Employees_ByYearOfBirth : AbstractJavaScriptIndexCreationTask
{
    public class Result
    {
        public int YearOfBirth { get; set; }
    }

    public Employees_ByYearOfBirth()
    {
        Maps = new HashSet<string>
        {
            @"map('Employees', function (employee){ 
                        return {
                            Birthday : employee.Birthday.Year 
                        } 
                   })"
        };
    }
}

Query the index:

IList<Employee> employees = session
    .Query<Employees_ByYearOfBirth.Result, Employees_ByYearOfBirth>()
    .Where(x => x.YearOfBirth == 1963)
    .OfType<Employee>()
    .ToList();
IList<Employee> employees = session
    .Advanced
    .DocumentQuery<Employees_ByYearOfBirth.Result, Employees_ByYearOfBirth>()
    .WhereEquals(x => x.YearOfBirth, 1963)
    .OfType<Employee>()
    .ToList();
from index 'Employees/ByYearOfBirth'
where YearOfBirth = 1963

Indexing Nested Data

If your document contains nested data, e.g. Employee contains Address, you can index on its fields by accessing them directly in the index. Let's say that we would like to create an index that returns all employees that were born in a specific Country:

Index definition:

public class Employees_ByCountry : AbstractIndexCreationTask<Employee>
{
    public class Result
    {
        public string Country { get; set; }
    }

    public Employees_ByCountry()
    {
        Map = employees => from employee in employees
                           select new Result
                           {
                               Country = employee.Address.Country
                           };
    }
}
public class Employees_ByCountry : AbstractJavaScriptIndexCreationTask
{
    public class Result
    {
        public string Country { get; set; }
    }

    public Employees_ByCountry()
    {
        Maps = new HashSet<string>
        {
            @"map('Employees', function (employee){ 
                        return {
                            Country : employee.Address.Country 
                         } 
                   })"
        };
    }
}

Query the index:

IList<Employee> employees = session
    .Query<Employees_ByCountry.Result, Employees_ByCountry>()
    .Where(x => x.Country == "USA")
    .OfType<Employee>()
    .ToList();
IList<Employee> employees = session
    .Advanced
    .DocumentQuery<Employees_ByCountry.Result, Employees_ByCountry>()
    .WhereEquals(x => x.Country, "USA")
    .OfType<Employee>()
    .ToList();
from index 'Employees/ByCountry'
where Country = 'USA'

If a document relationship is represented by the document's ID, you can use the LoadDocument method to retrieve such a document. More about it can be found here.

Indexing Multiple Collections

Read the article dedicated to Multi-Map indexes here.

Indexing Missing Fields

By default, indexes will not index a document that contains none of the specified fields. This behavior can be changed using the Indexing.IndexEmptyEntries configuration option.

The option Indexing.IndexMissingFieldsAsNull determines whether missing fields in documents are indexed with the value null, or not indexed at all.