Skip to main content

Project Index Query Results

Select

Example I - Projecting individual fields of the document:

var projectedResults = session
// Query the index
.Query<Employees_ByNameAndTitle.IndexEntry, Employees_ByNameAndTitle>()
// Can filter by any index-field, e.g.filter by index-field 'Title'
.Where(x => x.Title == "sales representative")
// Call 'Select' to return only the first and last name per matching document
.Select(x => new
{
EmployeeFirstName = x.FirstName,
EmployeeLastName = x.LastName
})
.ToList();

// Each resulting object in the list is Not an 'Employee' entity,
// it is a new object containing ONLY the fields specified in the Select
// ('EmployeeFirstName' & 'EmployeeLastName').
  • Type of projection fields:

    • In the above example, the fields to return by the projection that are specified in the Select method
      (x.FirstName & x.LastName) are recognized by the compiler as fields of the IndexEntry class.

    • If you wish to specify fields from the original 'Employee' class type then follow this example that uses OfType.

  • Source of projection fields:

    • Since the index-fields in this example are not Stored in the index, and no projection behavior was defined,
      resulting values for FirstName & LastName will be retrieved from the matching Employee document in the storage.

    • This behavior can be modified by setting the projection behavior used when querying a static-index.

Example II - Projecting stored fields:

var projectedResults = session
.Query<Employees_ByNameAndTitleWithStoredFields.IndexEntry,
Employees_ByNameAndTitleWithStoredFields>()
.Select(x => new
{
// Project fields 'FirstName' and 'LastName' which are STORED in the index
EmployeeFirstName = x.FirstName,
EmployeeLastName = x.LastName
})
.ToList();
  • In this example, the projected fields (FirstName and LastName) are stored in the index,
    so by default, the resulting values will come directly from the index and Not from the Employee document in the storage.

  • This behavior can be modified by setting the projection behavior used when querying a static-index.

Example III - Projecting arrays and objects:

var projectedResults = session
.Query<Orders_ByCompanyAndShipToAndLines.IndexEntry, Orders_ByCompanyAndShipToAndLines>()
.Where(x => x.Company == "companies/65-A")
.Select(x => new
{
// Retrieve a property from an object
ShipToCity = x.ShipTo.City,
// Retrieve all product names from the Lines array
Products = x.Lines.Select(y => y.ProductName)
})
.ToList();

Example IV - Projection with expression:

var projectedResults = session
.Query<Employees_ByNameAndTitle.IndexEntry, Employees_ByNameAndTitle>()
.Select(x => new
{
// Any expression can be provided for the projected content
FullName = x.FirstName + " " + x.LastName
})
.ToList();

Example V - Projection with calculations:

var projectedResults = session
.Query<Orders_ByCompanyAndShipToAndLines.IndexEntry, Orders_ByCompanyAndShipToAndLines>()
.Select(x => new
{
// Any calculations can be done within a projection
TotalProducts = x.Lines.Count,
TotalDiscountedProducts = x.Lines.Count(x => x.Discount > 0),
TotalPrice = x.Lines.Sum(l => l.PricePerUnit * l.Quantity)
})
.ToList();

Example VI - Projecting using functions:

var projectedResults =
// Use LINQ query syntax notation
(from x in session
.Query<Employees_ByNameAndTitle.IndexEntry, Employees_ByNameAndTitle>()
// Define a function
let format =
(Func<Employees_ByNameAndTitle.IndexEntry, string>)(p =>
p.FirstName + " " + p.LastName)
select new
{
// Call the function from the projection
FullName = format(x)
})
.ToList();

Example VII - Projecting using a loaded document:

var projectedResults = 
// Use LINQ query syntax notation
(from o in session
.Query<Orders_ByCompanyAndShippedAt.IndexEntry, Orders_ByCompanyAndShippedAt>()
// Use RavenQuery.Load to load the related Company document
let c = RavenQuery.Load<Company>(o.Company)
select new
{
CompanyName = c.Name, // info from the related Company document
ShippedAt = o.ShippedAt // info from the Order document
})
.ToList();

Example VIII - Projection with dates:

var projectedResults = session
.Query<Employees_ByFirstNameAndBirthday.IndexEntry, Employees_ByFirstNameAndBirthday>()
.Select(x => new
{
DayOfBirth = x.Birthday.Day,
MonthOfBirth = x.Birthday.Month,
Age = DateTime.Today.Year - x.Birthday.Year
})
.ToList();

Example IX - Projection with raw JavaScript code:

var projectedResults = session
.Query<Employees_ByFirstNameAndBirthday.IndexEntry, Employees_ByFirstNameAndBirthday>()
.Select(x => new
{
// Provide a JavaScript expression to the RavenQuery.Raw method
Date = RavenQuery.Raw<DateTime>("new Date(Date.parse(x.Birthday))"),
Name = RavenQuery.Raw(x.FirstName, "substr(0,3)")
})
.ToList();

Example X - Projection with metadata:

var projectedResults = session
.Query<Employees_ByFirstNameAndBirthday.IndexEntry, Employees_ByFirstNameAndBirthday>()
.Select(x => new
{
Name = x.FirstName,
Metadata = RavenQuery.Metadata(x) // Get the metadata
})
.ToList();

ProjectInto

  • Instead of Select, you can use ProjectInto to project all public fields from a generic type.

  • The results will be projected into objects of the specified projection class.

var projectedResults = session
.Query<Companies_ByContactDetailsAndPhone.IndexEntry, Companies_ByContactDetailsAndPhone>()
.Where(x => x.ContactTitle == "owner")
// Call 'ProjectInto' instead of using 'Select'
// Pass the projection class
.ProjectInto<ContactDetails>()
.ToList();

// Each resulting object in the list is Not a 'Company' entity,
// it is an object of type 'ContactDetails'.

SelectFields

The SelectFields method can only be used by a Document Query.
It has two overloads:

// 1) Select fields to project by the projection class type
IDocumentQuery<TProjection> SelectFields<TProjection>();

// 2) Select specific fields to project
IDocumentQuery<TProjection> SelectFields<TProjection>(params string[] fields);

Using projection class type:

  • The projection class fields are the fields that you want to project from the 'IndexEntry' class.
// Query an index with DocumentQuery
var projectedResults = session.Advanced
.DocumentQuery<Products_ByNamePriceQuantityAndUnits.IndexEntry,
Products_ByNamePriceQuantityAndUnits>()
// Call 'SelectFields'
// Pass the projection class type
.SelectFields<ProductDetails>()
.ToList();

// Each resulting object in the list is Not a 'Product' entity,
// it is an object of type 'ProductDetails'.

Using specific fields:

  • The fields specified are the fields that you want to project from the projection class.
// Define an array with the field names that will be projected
var fields = new string[] {
"ProductName",
"PricePerUnit"
};

// Query an index with DocumentQuery
var projectedResults = session.Advanced
.DocumentQuery<Companies_ByContactDetailsAndPhone.IndexEntry,
Companies_ByContactDetailsAndPhone>()
// Call 'SelectFields'
// Pass the projection class type & the fields to be projected from it
.SelectFields<ProductDetails>(fields)
.ToList();

// Each resulting object in the list is Not a 'Product' entity,
// it is an object of type 'ProductDetails' containing data ONLY for the specified fields.

Projection behavior with a static-index

  • By default, when querying a static-index and projecting query results,
    the server will try to retrieve the fields' values from the fields stored in the index.
    If the index does Not store those fields then the fields' values will be retrieved from the documents.

  • This behavior can be modified by setting the projection behavior.

  • Note: Storing fields in the index can increase query performance when projecting,
    but this comes at the expense of the disk space used by the index.

Example:

var projectedResults = session
.Query<Employees_ByNameAndTitleWithStoredFields.IndexEntry,
Employees_ByNameAndTitleWithStoredFields>()
// Call 'Customize'
// Pass the requested projection behavior to the 'Projection' method
.Customize(x => x.Projection(ProjectionBehavior.FromIndexOrThrow))
// Select the fields that will be returned by the projection
.Select(x => new EmployeeDetails
{
FirstName = x.FirstName,
Title = x.Title
})
.ToList();

The projection behavior in the above example is set to FromIndexOrThrow and so the following applies:

  • Field FirstName is stored in the index so the server will fetch its values from the index.

  • However, field Title is Not stored in the index so an exception will be thrown when the query is executed.

Syntax for projection behavior:

// For Query:
IDocumentQueryCustomization Projection(ProjectionBehavior projectionBehavior);

// For DocumentQuery:
IDocumentQuery<TProjection> SelectFields<TProjection>(
ProjectionBehavior projectionBehavior, params string[] fields);

IDocumentQuery<TProjection> SelectFields<TProjection>(
ProjectionBehavior projectionBehavior);

// Projection behavior options:
public enum ProjectionBehavior {
Default,
FromIndex,
FromIndexOrThrow,
FromDocument,
FromDocumentOrThrow
}
  • Default
    Retrieve values from the stored index fields when available.
    If fields are not stored then get values from the document,
    a field that is not found in the document is skipped.

  • FromIndex
    Retrieve values from the stored index fields when available.
    A field that is not stored in the index is skipped.

  • FromIndexOrThrow
    Retrieve values from the stored index fields when available.
    An exception is thrown if the index does not store the requested field.

  • FromDocument
    Retrieve values directly from the documents store.
    A field that is not found in the document is skipped.

  • FromDocumentOrThrow
    Retrieve values directly from the documents store.
    An exception is thrown if the document does not contain the requested field.

OfType

  • When making a projection query, converting the shape of the matching documents to the requested projection is done on the server-side.

  • On the other hand, OfType is a client-side type conversion that is only used to map the resulting objects to the provided type.

  • We differentiate between the following cases:

    • Using OfType with projection queries - resulting objects are Not tracked by the session
    • Using OfType with non-projection queries - resulting documents are tracked by the session

Using OfType with projection queries:

// Make a projection query:
// ========================

var projectedResults = session
.Query<Companies_ByContactDetailsAndPhone.IndexEntry, Companies_ByContactDetailsAndPhone>()
// Here we filter by an IndexEntry field
// The compiler recognizes 'x' as an IndexEntry type
.Where(x => x.ContactTitle == "owner")
// Now, if you wish to project based on the 'Company' document
// then use 'OfType' to let the compiler recognize the type
.OfType<Company>()
// Select which fields from the matching document will be returned
.Select(x => new
{
// The compiler now recognizes 'x' as a 'Company' class type
// e.g. 'Name' & 'Address.Country' are properties of the 'Company' document
CompanyName = x.Name,
CompanyCountry = x.Address.Country
})
.ToList();

// Each resulting object has the 'CompanyName' & 'CompanyCountry' fields specified in the projection.
// The resulting objects are NOT TRACKED by the session.

Using OfType with non-projection queries:

// Make a non-projecting query:
// ============================

List<Company> results = session
.Query<Companies_ByContactDetailsAndPhone.IndexEntry, Companies_ByContactDetailsAndPhone>()
// Here we filter by an IndexEntry field
// The compiler recognizes 'x' as an IndexEntry type
.Where(x => x.ContactTitle == "owner")
// A type conversion is now required for the compiler to understand the resulting objects' shape.
// Use 'OfType to let the compiler know that resulting objects are of type 'Company' documents.
.OfType<Company>()
.ToList();

// The resulting objects are full 'Company' document entities (not projected).
// Each 'Company' entity is TRACKED by the session.