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').
var projectedResults = await asyncSession
     // 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
    })
    .ToListAsync();

// 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').
public class Employees_ByNameAndTitle : AbstractIndexCreationTask<Employee>
{
    public class IndexEntry
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Title { get; set; }
    }
    
    public Employees_ByNameAndTitle()
    {
        Map = employees => from employee in employees
            
            select new IndexEntry
            {
                FirstName = employee.FirstName,
                LastName = employee.LastName,
                Title = employee.Title
            };
    }
}
from index "Employees/ByNameAndTitle"
where Title == "sales representative"
select FirstName as EmployeeFirstName, LastName as 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();
var projectedResults = await asyncSession
    .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
    })
    .ToListAsync();
public class Employees_ByNameAndTitleWithStoredFields : AbstractIndexCreationTask<Employee>
{    
    public class IndexEntry
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Title { get; set; }
    }
    
    public Employees_ByNameAndTitleWithStoredFields()
    {
        Map = employees => from employee in employees
            select new IndexEntry
            {
                FirstName = employee.FirstName,
                LastName = employee.LastName,
                Title = employee.Title
            };
        
        // Store some fields in the index:
        Stores.Add(x => x.FirstName, FieldStorage.Yes);
        Stores.Add(x => x.LastName, FieldStorage.Yes);
    }
}
from index "Employees/ByNameAndTitleWithStoredFields"
select FirstName as EmployeeFirstName, LastName as EmployeeLastName
  • 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();
var projectedResults = await asyncSession
    .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)
    })
    .ToListAsync();
public class Orders_ByCompanyAndShipToAndLines : AbstractIndexCreationTask<Order>
{
    public class IndexEntry
    {
        public string Company { get; set; }
        public Address ShipTo { get; set; }
        public List<OrderLine> Lines { get; set; }
    }
    
    public Orders_ByCompanyAndShipToAndLines()
    {
        Map = orders => from order in orders
            select new IndexEntry
            {
                Company = order.Company,
                ShipTo = order.ShipTo,
                Lines = order.Lines
            };
    }
}

// public class Address
// {
//     public string Line1 { get; set; }
//     public string Line2 { get; set; }
//     public string City { get; set; }
//     public string Region { get; set; }
//     public string PostalCode { get; set; }
//     public string Country { get; set; }
//     public Location Location { get; set; }
// }

// public class OrderLine
// {
//     public string Product { get; set; }
//     public string ProductName { get; set; }
//     public decimal PricePerUnit { get; set; }
//     public int Quantity { get; set; }
//     public decimal Discount { get; set; }
// }
// Using simple expression:
from index "Orders/ByCompanyAndShipToAndLines"
where Company == "companies/65-A"
select ShipTo.City as ShipToCity, Lines[].ProductName as Products

// Using JavaScript object literal syntax:
from index "Orders/ByCompanyAndShipToAndLines" as x
where Company == "companies/65-A"
select {
    ShipToCity: x.ShipTo.City,
    Products: x.Lines.map(y => y.ProductName)
}

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();
var projectedResults = await asyncSession
    .Query<Employees_ByNameAndTitle.IndexEntry, Employees_ByNameAndTitle>()
    .Select(x => new
    {
        // Any expression can be provided for the projected content
        FullName = x.FirstName + " " + x.LastName
    })
    .ToListAsync();
public class Employees_ByNameAndTitle : AbstractIndexCreationTask<Employee>
{
    public class IndexEntry
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Title { get; set; }
    }
    
    public Employees_ByNameAndTitle()
    {
        Map = employees => from employee in employees
            
            select new IndexEntry
            {
                FirstName = employee.FirstName,
                LastName = employee.LastName,
                Title = employee.Title
            };
    }
}
from index "Employees/ByNameAndTitle" as x
select 
{ 
    FullName : x.FirstName + " " + x.LastName 
}

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();
var projectedResults = await asyncSession
    .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)
    })
    .ToListAsync();
public class Orders_ByCompanyAndShipToAndLines : AbstractIndexCreationTask<Order>
{
    public class IndexEntry
    {
        public string Company { get; set; }
        public Address ShipTo { get; set; }
        public List<OrderLine> Lines { get; set; }
    }
    
    public Orders_ByCompanyAndShipToAndLines()
    {
        Map = orders => from order in orders
            select new IndexEntry
            {
                Company = order.Company,
                ShipTo = order.ShipTo,
                Lines = order.Lines
            };
    }
}

// public class Address
// {
//     public string Line1 { get; set; }
//     public string Line2 { get; set; }
//     public string City { get; set; }
//     public string Region { get; set; }
//     public string PostalCode { get; set; }
//     public string Country { get; set; }
//     public Location Location { get; set; }
// }

// public class OrderLine
// {
//     public string Product { get; set; }
//     public string ProductName { get; set; }
//     public decimal PricePerUnit { get; set; }
//     public int Quantity { get; set; }
//     public decimal Discount { get; set; }
// }
from index "Orders/ByCompanyAndShipToAndLines" as x
select {
    TotalProducts: x.Lines.length,
    TotalDiscountedProducts: x.Lines.filter(x => x.Discount > 0).length,
    TotalPrice: x.Lines
                 .map(l => l.PricePerUnit * l.Quantity)
                 .reduce((a, b) => a + b, 0)
}

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();
var projectedResults =
    // Use LINQ query syntax notation
    await (from x in asyncSession
                .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)
            })
        .ToListAsync();
public class Employees_ByNameAndTitle : AbstractIndexCreationTask<Employee>
{
    public class IndexEntry
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Title { get; set; }
    }
    
    public Employees_ByNameAndTitle()
    {
        Map = employees => from employee in employees
            
            select new IndexEntry
            {
                FirstName = employee.FirstName,
                LastName = employee.LastName,
                Title = employee.Title
            };
    }
}
declare function output(x) {
var format = p => p.FirstName + " " + p.LastName;
    return { FullName: format(x) };
}
from index "Employees/ByNameAndTitle" as e
select output(e)

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();
// Use LINQ query syntax notation
var projectedResults = 
    await (from o in asyncSession
                .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
        })
    .ToListAsync();
public class Orders_ByCompanyAndShippedAt : AbstractIndexCreationTask<Order>
{
    public class IndexEntry
    {
        public string Company { get; set; }
        public DateTime? ShippedAt { get; set; }
    }
    
    public Orders_ByCompanyAndShippedAt()
    {
        Map = orders => from order in orders
            
            select new IndexEntry
            {
                Company = order.Company,
                ShippedAt = order.ShippedAt
            };
    }
}
from index "Orders/ByCompanyAndShippedAt" as o
load o.Company as c
select {
	CompanyName: c.Name,
	ShippedAt: o.ShippedAt
}

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();
var projectedResults = await asyncSession
    .Query<Employees_ByFirstNameAndBirthday.IndexEntry, Employees_ByFirstNameAndBirthday>()
    .Select(x => new
    {
        DayOfBirth = x.Birthday.Day,
        MonthOfBirth = x.Birthday.Month,
        Age = DateTime.Today.Year - x.Birthday.Year
    })
    .ToListAsync();
public class Employees_ByFirstNameAndBirthday : AbstractIndexCreationTask<Employee>
{
    public class IndexEntry
    {
        public string FirstName { get; set; }
        public DateTime Birthday { get; set; }
    }
    
    public Employees_ByFirstNameAndBirthday()
    {
        Map = employees => from employee in employees
            
            select new IndexEntry
            {
                FirstName = employee.FirstName,
                Birthday = employee.Birthday
            };
    }
}
from index "Employees/ByFirstNameAndBirthday" as x 
select { 
    DayOfBirth: new Date(Date.parse(x.Birthday)).getDate(), 
    MonthOfBirth: new Date(Date.parse(x.Birthday)).getMonth() + 1,
    Age: new Date().getFullYear() - new Date(Date.parse(x.Birthday)).getFullYear() 
}

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();
var projectedResults = await asyncSession
    .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)")
    })
    .ToListAsync();
public class Employees_ByFirstNameAndBirthday : AbstractIndexCreationTask<Employee>
{
    public class IndexEntry
    {
        public string FirstName { get; set; }
        public DateTime Birthday { get; set; }
    }
    
    public Employees_ByFirstNameAndBirthday()
    {
        Map = employees => from employee in employees
            
            select new IndexEntry
            {
                FirstName = employee.FirstName,
                Birthday = employee.Birthday
            };
    }
}
from index "Employees/ByFirstNameAndBirthday" as x 
select {
    Date: new Date(Date.parse(x.Birthday)), 
    Name: x.FirstName.substr(0,3)
}

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();
var projectedResults = await asyncSession
    .Query<Employees_ByFirstNameAndBirthday.IndexEntry, Employees_ByFirstNameAndBirthday>()
    .Select(x => new
    {
        Name = x.FirstName,
        Metadata = RavenQuery.Metadata(x) // Get the metadata
    })
    .ToListAsync();
public class Employees_ByFirstNameAndBirthday : AbstractIndexCreationTask<Employee>
{
    public class IndexEntry
    {
        public string FirstName { get; set; }
        public DateTime Birthday { get; set; }
    }
    
    public Employees_ByFirstNameAndBirthday()
    {
        Map = employees => from employee in employees
            
            select new IndexEntry
            {
                FirstName = employee.FirstName,
                Birthday = employee.Birthday
            };
    }
}
from index "Employees/ByFirstNameAndBirthday" as x 
select {
     Name : x.FirstName, 
     Metadata : getMetadata(x)
}

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'.
var projectedResults = await asyncSession
    .Query<Companies_ByContactDetailsAndPhone.IndexEntry, Companies_ByContactDetailsAndPhone>()
    .Where(x => x.ContactTitle == "owner")
     // Call 'ProjectInto' instead of using 'Select'
     // Pass the projection class
    .ProjectInto<ContactDetails>()
    .ToListAsync();

// Each resulting object in the list is Not a 'Company' entity,
// it is an object of type 'ContactDetails'.
public class Companies_ByContactDetailsAndPhone : AbstractIndexCreationTask<Company>
{
    public class IndexEntry
    {
        public string ContactName { get; set; }
        public string ContactTitle { get; set; }
        public string Phone { get; set; }
    }
    
    public Companies_ByContactDetailsAndPhone()
    {
        Map = companies => companies
            .Select(x => new IndexEntry
            {
                ContactName = x.Contact.Name,
                ContactTitle = x.Contact.Title,
                Phone = x.Phone
            });
    }
}
public class ContactDetails
{
    // The projection class contains field names from the index-fields
    public string ContactName { get; set; }
    public string ContactTitle { get; set; }
}
from index "Companies/ByContactDetailsAndPhone"
where ContactTitle == "owner"
select ContactName, ContactTitle

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'.
// Query an index with DocumentQuery
var projectedResults = await asyncSession.Advanced
    .AsyncDocumentQuery<Products_ByNamePriceQuantityAndUnits.IndexEntry, 
        Products_ByNamePriceQuantityAndUnits>()
     // Call 'SelectFields'
     // Pass the projection class type
    .SelectFields<ProductDetails>()
    .ToListAsync();

// Each resulting object in the list is Not a 'Product' entity,
// it is an object of type 'ProductDetails'.
public class Products_ByNamePriceQuantityAndUnits : AbstractIndexCreationTask<Product>
{
    public class IndexEntry
    {
        public string ProductName { get; set; }
        public string QuantityPerUnit { get; set; }
        public decimal PricePerUnit { get; set; }
        public int UnitsInStock { get; set; }
        public int UnitsOnOrder { get; set; }
    }
    
    public Products_ByNamePriceQuantityAndUnits()
    {
        Map = products => from product in products
            
            select new IndexEntry
            {
                ProductName = product.Name,
                QuantityPerUnit = product.QuantityPerUnit,
                PricePerUnit = product.PricePerUnit,
                UnitsInStock = product.UnitsInStock,
                UnitsOnOrder = product.UnitsOnOrder
            };
    }
}
public class ProductDetails
{
    // The projection class contains field names from the index-fields
    public string ProductName { get; set; }
    public decimal PricePerUnit { get; set; }
    public int UnitsInStock { get; set; }
}
from index "Products/ByNamePriceQuantityAndUnits"
select ProductName, PricePerUnit, UnitsInStock

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.
// Define an array with the field names that will be projected
var fields = new string[] {
    "ProductName",
    "PricePerUnit"
};

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

// 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.
public class Products_ByNamePriceQuantityAndUnits : AbstractIndexCreationTask<Product>
{
    public class IndexEntry
    {
        public string ProductName { get; set; }
        public string QuantityPerUnit { get; set; }
        public decimal PricePerUnit { get; set; }
        public int UnitsInStock { get; set; }
        public int UnitsOnOrder { get; set; }
    }
    
    public Products_ByNamePriceQuantityAndUnits()
    {
        Map = products => from product in products
            
            select new IndexEntry
            {
                ProductName = product.Name,
                QuantityPerUnit = product.QuantityPerUnit,
                PricePerUnit = product.PricePerUnit,
                UnitsInStock = product.UnitsInStock,
                UnitsOnOrder = product.UnitsOnOrder
            };
    }
}
public class ProductDetails
{
    // The projection class contains field names from the index-fields
    public string ProductName { get; set; }
    public decimal PricePerUnit { get; set; }
    public int UnitsInStock { get; set; }
}
from index "Companies/ByContactDetailsAndPhone" 
select ProductName, PricePerUnit

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();
var projectedResults = session.Advanced
    .DocumentQuery<Employees_ByNameAndTitleWithStoredFields.IndexEntry,
        Employees_ByNameAndTitleWithStoredFields>()
     // Pass the requested projection behavior to the 'SelectFields' method
    .SelectFields<EmployeeDetails>(ProjectionBehavior.FromIndexOrThrow)
    .ToList();
var projectedResults = session.Advanced
    // Define an RQL query that returns a projection
    .RawQuery<EmployeeDetails>(
        @"from index 'Employees/ByNameAndTitleWithStoredFields' select FirstName, Title")
    // Pass the requested projection behavior to the 'Projection' method
    .Projection(ProjectionBehavior.FromIndexOrThrow)
    .ToList();
public class Employees_ByNameAndTitleWithStoredFields : AbstractIndexCreationTask<Employee>
{    
    public class IndexEntry
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Title { get; set; }
    }
    
    public Employees_ByNameAndTitleWithStoredFields()
    {
        Map = employees => from employee in employees
            select new IndexEntry
            {
                FirstName = employee.FirstName,
                LastName = employee.LastName,
                Title = employee.Title
            };
        
        // Store some fields in the index:
        Stores.Add(x => x.FirstName, FieldStorage.Yes);
        Stores.Add(x => x.LastName, FieldStorage.Yes);
    }
}
public class EmployeeDetails
{
    public string FirstName { get; set; } 
    public string Title { get; set; }
}
from index "Employees/ByNameAndTitleWithStoredFields"
select FirstName, Title

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.
// Make a projection query:
// ========================

var projectedResults = await asyncSession
    .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
    })
    .ToListAsync();

// Each resulting object has the 'CompanyName' & 'CompanyCountry' fields specified in the projection.
// The resulting objects are NOT TRACKED by the session.
public class Companies_ByContactDetailsAndPhone : AbstractIndexCreationTask<Company>
{
    public class IndexEntry
    {
        public string ContactName { get; set; }
        public string ContactTitle { get; set; }
        public string Phone { get; set; }
    }
    
    public Companies_ByContactDetailsAndPhone()
    {
        Map = companies => companies
            .Select(x => new IndexEntry
            {
                ContactName = x.Contact.Name,
                ContactTitle = x.Contact.Title,
                Phone = x.Phone
            });
    }
}
from index "Companies/ByContactDetailsAndPhone"
where ContactTitle == "owner"

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.
// Make a non-projecting query:
// ============================

List<Company> results = await asyncSession
    .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>()
    .ToListAsync();

// The resulting objects are full 'Company' document entities (not projected).
// Each 'Company' entity is TRACKED by the session.
public class Companies_ByContactDetailsAndPhone : AbstractIndexCreationTask<Company>
{
    public class IndexEntry
    {
        public string ContactName { get; set; }
        public string ContactTitle { get; set; }
        public string Phone { get; set; }
    }
    
    public Companies_ByContactDetailsAndPhone()
    {
        Map = companies => companies
            .Select(x => new IndexEntry
            {
                ContactName = x.Contact.Name,
                ContactTitle = x.Contact.Title,
                Phone = x.Phone
            });
    }
}
from index "Companies/ByContactDetailsAndPhone"
where ContactTitle == "owner"