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 (in other words they indicate which documents can be searched by which fields).

These mapping functions are the LINQ-based functions and can be considered to be the indexes core.

What can be indexed?

The easiest answer to this question is practically anything. You can:

Various articles in this part of documentation will describe possibilities in detail.

Basics - indexing single fields

To start, let's create an index that will help us to search for Employees by their FirstName, LastName (or both).

  • first, let's start creating an index called Employees/ByFirstAndLastName

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

You probably noticed that we're passing Employee as a generic parameter to AbstractIndexCreationTask. Thanks to that, our indexing function will have a strongly-typed syntax. If you are not familiar with AbstractIndexCreationTask, then you should read this article before proceeding.

  • the next step is to create an indexing function itself, and to do it we need to set the Map property with our function in 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
		});
}
  • the final step is to deploy it (omitted) to server and issue a query using session Query method:

IList<Employee> employees = session
	.Query<Employee, Employees_ByFirstAndLastName>()
	.Where(x => x.FirstName == "Robert")
	.ToList();
IList<Employee> employees = session
	.Query<Employee>("Employees/ByFirstAndLastName")
	.Where(x => x.FirstName == "Robert")
	.ToList();

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
					};
	}
}

Convention

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

from doc in docs.Employees
select new
{
	FirstName = doc.FirstName,
	LastName = doc.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 Employees collection. To change the collection you need to change Employees to desired collection name or remove it and leave only docs to index all documents.

Combining multiple fields together

Since under each index there is a LINQ-function, you can combine multiple fields in one, if necessary.

Example I

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
					{
						FullName = employee.FirstName + " " + employee.LastName
					};
	}
}

// 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();

Example II

Information

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

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

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
                           {
                                Query = new[]
                                {
                                    employee.FirstName,
                                    employee.LastName,
                                    employee.Title,
                                    employee.Address.City
                                }
                           };

        Index("Query", FieldIndexing.Analyzed);
    }
}

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();

Indexing partial field data

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

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
					{
						Birthday = employee.Birthday
					};
	}
}

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();

However, RavenDB gives you an ability to extract field data and to index by it, so a different way to achieve our goal will look as follows:

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
					{
						YearOfBirth = employee.Birthday.Year
					};
	}
}

IList<Employee> employees = session
	.Query<Employees_ByYearOfBirth.Result, Employees_ByYearOfBirth>()
	.Where(x => x.YearOfBirth == 1963)
	.OfType<Employee>()
	.ToList();

Indexing nested data

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

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
					{
						Country = employee.Address.Country
					};
	}
}

IList<Employee> employees = session
	.Query<Employees_ByCountry.Result, Employees_ByCountry>()
	.Where(x => x.Country == "USA")
	.OfType<Employee>()
	.ToList();

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

Indexing multiple collections

Please read an article dedicated to Multi-Map indexes that can be found here.