Projections

There are couple a couple of ways to perform projections in RavenDB:

Select - basic projections

The most basic projection can be done using LINQ Select method:

var results = session
	.Query<Employee, Employees_ByFirstAndLastName>()
	.Select(x => new
	{
		FirstName = x.FirstName,
		LastName = x.LastName
	})
	.ToList();
var results = session
	.Advanced
	.DocumentQuery<Employee, Employees_ByFirstAndLastName>()
	.Select(x => new
	{
		FirstName = x.FirstName,
		LastName = x.LastName
	})
	.ToList();
QueryResult result = store
	.DatabaseCommands
	.Query(
		"Employees/ByFirstAndLastName",
		new IndexQuery
			{
				FieldsToFetch = new[]
				{
					"FirstName", 
					"LastName"
				}
			});
public class Employees_ByFirstAndLastName : AbstractIndexCreationTask<Employee>
{
	public Employees_ByFirstAndLastName()
	{
		Map = employees => from employee in employees
						   select new
							{
								FirstName = employee.FirstName,
								LastName = employee.LastName
							};
	}
}

This will issue a query to a database, requesting only FirstName and LastName from all documents that index entries match query predicate from Employees/ByFirstAndLastName index. What does it mean? It means that, if index entry matches our query predicate, then we will try to extract all requested fields from that particular entry and if all requested fields are available in there, then we do not download it from storage. Index Employees/ByFirstAndLastName used in above query is not storing any fields so documents will be fetched from storage.

Projections and Stored fields

If projection function only requires fields that are stored, then document will not be loaded from storage and all data will come from index directly. This can increase query performance (by the cost of disk space used) in many situations when whole document is not needed. You can read more about field storing here.

Raven/ImplicitFetchFieldsFromDocumentMode setting can be altered to change the behavior of field fetching. By default it allows fetching fields from document if index is missing them (they are not stored), but this can be changed to skipping those fields or even throwing an exception. Read more about this configuration option here.

So following above rule, if we create index that stores FirstName and LastName and request only those fields in query, then data will come from index directly.

var results = session
	.Query<Employee, Employees_ByFirstAndLastNameWithStoredFields>()
	.Select(x => new
	{
		FirstName = x.FirstName,
		LastName = x.LastName
	})
	.ToList();
var results = session
	.Advanced
	.DocumentQuery<Employee, Employees_ByFirstAndLastNameWithStoredFields>()
	.Select(x => new
	{
		FirstName = x.FirstName,
		LastName = x.LastName
	})
	.ToList();
QueryResult result = store
	.DatabaseCommands
	.Query(
		"Employees/ByFirstAndLastNameWithStoredFields",
		new IndexQuery
		{
			FieldsToFetch = new[]
				{
					"FirstName", 
					"LastName"
				}
		});
public class Employees_ByFirstAndLastNameWithStoredFields : AbstractIndexCreationTask<Employee>
{
	public Employees_ByFirstAndLastNameWithStoredFields()
	{
		Map = employees => from employee in employees
						   select new
						   {
							   FirstName = employee.FirstName,
							   LastName = employee.LastName
						   };

		Store(x => x.FirstName, FieldStorage.Yes);
		Store(x => x.LastName, FieldStorage.Yes);
	}
}

ProjectFromIndexFieldsInto

This method uses reflection to extract all public fields and properties to fetch and perform projection to the requested type.

public class EmployeeFirstAndLastName
{
	public string FirstName { get; set; }

	public string LastName { get; set; }
}

IList<EmployeeFirstAndLastName> results = session
	.Query<Employee, Employees_ByFirstAndLastName>()
	.ProjectFromIndexFieldsInto<EmployeeFirstAndLastName>()
	.ToList();
IList<EmployeeFirstAndLastName> results = session
	.Advanced
	.DocumentQuery<Employee, Employees_ByFirstAndLastName>()
	.SelectFields<EmployeeFirstAndLastName>()
	.ToList();
public class Employees_ByFirstAndLastName : AbstractIndexCreationTask<Employee>
{
	public Employees_ByFirstAndLastName()
	{
		Map = employees => from employee in employees
						   select new
							{
								FirstName = employee.FirstName,
								LastName = employee.LastName
							};
	}
}

OfType (As)

OfType or As is a client-side projection. You can read more about it here.

TransformWith

Detailed article about transformer basics can be found here.