Project Index Query Results
-
This article provides examples of projecting query results when querying a static-index.
-
Prior to reading this article, please refer to query results projection overview for general knowledge about Projections and for dynamic-queries examples.
-
In this page:
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 theIndexEntry
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 forFirstName
&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
andLastName
) 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 useProjectInto
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"