Query by Facets


  • A Faceted Search provides an efficient way to explore and navigate through large datasets or search results.

  • Multiple filters (facets) are applied to narrow down the search results according to different attributes or categories.

Facets


Define an index

  • In order to make a faceted search, a static-index must be defined for the fields you want to query and apply facets on.

  • The examples in this article will be based on the following Class, Index, and Sample Data:

public class Camera
{
    public string Manufacturer { get; set; }
    public double Cost { get; set; }
    public double MegaPixels { get; set; }
    public int MaxFocalLength { get; set; }
    public int UnitsInStock { get; set; }
}
public class Cameras_ByFeatures : AbstractIndexCreationTask<Camera>
{
    public class IndexEntry
    {
        public string Brand { get; set; }
        public double Price { get; set; }
        public double MegaPixels { get; set; }
        public int MaxFocalLength { get; set; }
        public int UnitsInStock { get; set; }
    }
    
    public Cameras_ByFeatures()
    {
        Map = cameras => from camera in cameras
            select new
            {
                Brand = camera.Manufacturer,
                Price = camera.Cost,
                MegaPixels = camera.MegaPixels,
                MaxFocalLength = camera.MaxFocalLength,
                UnitsInStock = camera.UnitsInStock
            };
    }
}
// Creating sample data for the examples in this article:
// ======================================================

var cameras = new[]
{
  new Camera { Manufacturer = "Sony", Cost = 100, MegaPixels = 20.1, MaxFocalLength = 200, UnitsInStock = 10 },
  new Camera { Manufacturer = "Sony", Cost = 200, MegaPixels = 29, MaxFocalLength = 250, UnitsInStock = 15 },
  new Camera { Manufacturer = "Nikon", Cost = 120, MegaPixels = 22.3, MaxFocalLength = 300, UnitsInStock = 2 },
  new Camera { Manufacturer = "Nikon", Cost = 180, MegaPixels = 32, MaxFocalLength = 300, UnitsInStock = 5 },
  new Camera { Manufacturer = "Nikon", Cost = 220, MegaPixels = 40, MaxFocalLength = 300, UnitsInStock = 20 },
  new Camera { Manufacturer = "Canon", Cost = 200, MegaPixels = 30.4, MaxFocalLength = 400, UnitsInStock = 30 },
  new Camera { Manufacturer = "Olympus", Cost = 250, MegaPixels = 32.5, MaxFocalLength = 600, UnitsInStock = 4 },
  new Camera { Manufacturer = "Olympus", Cost = 390, MegaPixels = 40, MaxFocalLength = 600, UnitsInStock = 6 },
  new Camera { Manufacturer = "Fuji", Cost = 410, MegaPixels = 45, MaxFocalLength = 700, UnitsInStock = 1 },
  new Camera { Manufacturer = "Fuji", Cost = 590, MegaPixels = 45, MaxFocalLength = 700, UnitsInStock = 5 },
  new Camera { Manufacturer = "Fuji", Cost = 650, MegaPixels = 61, MaxFocalLength = 800, UnitsInStock = 17 },
  new Camera { Manufacturer = "Fuji", Cost = 850, MegaPixels = 102, MaxFocalLength = 800, UnitsInStock = 19 }
};

using (var session = store.OpenSession())
{
    foreach (var camera in cameras)
    {
        session.Store(camera);
    }

    session.SaveChanges();
}

Facets - Basics

Facets definition:


  • Define a list of facets by which to aggregate the data.

  • There are two Facet types:

    • Facet - returns a count for each unique term found in the specified index-field.
    • RangeFacet - returns a count per range within the specified index-field.

// Define a list of facets to query by:
// ====================================
List<FacetBase> facets = new List<FacetBase>
{
    // Define a Facet:
    // ===============
    new Facet
    {
        // Specify the index-field for which to get count of documents per unique ITEM
        // e.g. get the number of Camera documents for each unique Brand
        FieldName = "Brand",
        // Set a display name for this field in the results (optional) 
        DisplayFieldName = "Camera Brand"
    },

    // Define a RangeFacet:
    // ====================
    new RangeFacet<Cameras_ByFeatures.IndexEntry>
    {
        Ranges =
        {
            // Specify ranges within an index-field in order to get count per RANGE
            // e.g. get the number of Camera documents that cost below 200, between 200 & 400, etc...
            x => x.Price < 200,
            x => x.Price >= 200 && x.Price < 400,
            x => x.Price >= 400 && x.Price < 600,
            x => x.Price >= 600 && x.Price < 800,
            x => x.Price >= 800
        },
        // Set a display name for this field in the results (optional) 
        DisplayFieldName = "Camera Price"
    }
};

Query the index for facets results:


  • Query the index to get the aggregated facets information.

  • Either:

    • Pass the facets definition from above directly to the query

    • Or - construct a facet using a builder with the Fluent API option, as shown below.

Dictionary<string, FacetResult> results = session
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facets)
    .Execute();
Dictionary<string, FacetResult> results = await asyncSession
     // Query the index    
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facets)
    .ExecuteAsync();
Dictionary<string, FacetResult> results = session.Advanced
     // Query the index    
    .DocumentQuery<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facets)
    .Execute();
Dictionary<string, FacetResult> results = session
     // Query the index    
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Use a builder as follows:
    .AggregateBy(builder => builder
         // Specify the index-field (e.g. 'Brand') for which to get count per unique ITEM
        .ByField(x => x.Brand)
         // Set a display name for the field in the results (optional) 
        .WithDisplayName("Camera Brand"))
    .AndAggregateBy(builder => builder
         // Specify ranges within an index field (e.g. 'Price') in order to get count per RANGE
        .ByRanges(
            x => x.Price < 200,
            x => x.Price >= 200 && x.Price < 400,
            x => x.Price >= 400 && x.Price < 600,
            x => x.Price >= 600 && x.Price < 800,
            x => x.Price >= 800)
         // Set a display name for the field in the results (optional) 
        .WithDisplayName("Camera Price"))
    .Execute();
Dictionary<string, FacetResult> results = session.Advanced
      // Query the index
      // Provide the RQL string to the RawQuery method
     .RawQuery<Camera>(@"from index 'Cameras/ByFeatures'
                         select 
                             facet(Brand) as 'Camera Brand',
                             facet(Price < 200.0,
                                   Price >= 200.0 and Price < 400.0,
                                   Price >= 400.0 and Price < 600.0,
                                   Price >= 600.0 and Price < 800.0,
                                   Price >= 800.0) as 'Camera Price'")
     // Execute the query
    .ExecuteAggregation();
from index "Cameras/ByFeatures"
select
    facet(Brand) as "Camera Brand",
    facet(Price < 200.0,
          Price >= 200.0 and Price < 400.0,
          Price >= 400.0 and Price < 600.0,
          Price >= 600.0 and Price < 800.0,
          Price >= 800.0) as "Camera Price"

Query results:


  • Query results are Not the collection documents, they are of type:
    Dictionary<string, FacetResult> which is the facets results per index-field specified.

  • Using the sample data from this article, the resulting aggregations will be:

// The resulting aggregations per display name will contain:
// =========================================================

// For the "Camera Brand" Facet:
//     "canon"   - Count: 1
//     "fuji"    - Count: 4
//     "nikon"   - Count: 3
//     "olympus" - Count: 2
//     "sony"    - Count: 2

// For the "Camera Price" Ranges:
//     "Price < 200"                      - Count: 3
//     "Price >= 200.0 and Price < 400.0" - Count: 5
//     "Price >= 400.0 and Price < 600.0" - Count: 2
//     "Price >= 600.0 and Price < 800.0" - Count: 1
//     "Price >= 800.0"                   - Count: 1
// Get facets results for index-field 'Brand' using the display name specified:
// ============================================================================
var brandFacets = results["Camera Brand"];
var numberOfBrands = brandFacets.Values.Count; // 5 unique brands

// Get the aggregated facet value for a specific Brand:
var facetValue = brandFacets.Values[0];
// The brand name is available in the 'Range' property
// Note: value is lower-case since the default RavenDB analyzer was used by the index
Assert.Equal("canon", facetValue.Range);
// Number of documents for 'Canon' is available in the 'Count' property
Assert.Equal(1, facetValue.Count);

// Get facets results for index-field 'Price' using the display name specified:
// ============================================================================
var priceFacets = results["Camera Price"];
var numberOfRanges = priceFacets.Values.Count; // 5 different ranges

// Get the aggregated facet value for a specific Range:
facetValue = priceFacets.Values[0];
Assert.Equal("Price < 200", facetValue.Range); // The range string
Assert.Equal(3, facetValue.Count); // Number of documents in this range

Query further:


  • Typically, after presenting users with the initial facets results which show the available options,
    users can select specific categories to explore further.

  • For example, if the user selects Fuji and Nikon,
    then your next query can include a filter to focus only on those selected brands.

Dictionary<string, FacetResult> filteredResults = session
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Limit query results to the selected brands: 
    .Where(x => x.Brand.In("Fuji", "Nikon"))
    .AggregateBy(facets)
    .Execute();

Facets - Options

Facets definition:


  • Options are available only for the Facet type.

  • Available options:

    • Start - The position from which to send items (how many to skip).
    • PageSize - Number of items to return.
    • IncludeRemainingTerms - Show summary of items that didn't make it into the requested PageSize.
    • TermSortMode - Set the sort order on the resulting items.

// Define the list of facets to query by:
// ======================================
List<FacetBase> facetsWithOptions = new List<FacetBase>
{
    // Define a Facet:
    new Facet
    {
        // Specify the index-field for which to get count of documents per unique ITEM
        FieldName = "Brand",
        // Set some facets options
        Options = new FacetOptions
        {
            // Return the top 3 brands with most items count:
            PageSize = 3,
            TermSortMode = FacetTermSortMode.CountDesc
        }
    }
};

Query the index for facets results:


Dictionary<string, FacetResult> results = session
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facetsWithOptions)
    .Execute();
Dictionary<string, FacetResult> results = await asyncSession
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facetsWithOptions)
    .ExecuteAsync();
Dictionary<string, FacetResult> results = session.Advanced
     // Query the index
    .DocumentQuery<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facetsWithOptions)
    .Execute();
Dictionary<string, FacetResult> results = session
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Use a builder as follows:
    .AggregateBy(builder => builder
         // Specify an index-field (e.g. 'Brand') for which to get count per unique ITEM
        .ByField(x => x.Brand)
         // Specify the facets options
        .WithOptions(new FacetOptions
        {
            // Return the top 3 brands with most items count:
            PageSize = 3,
            TermSortMode = FacetTermSortMode.CountDesc
        }))
    .Execute();
Dictionary<string, FacetResult> results = session.Advanced
     // Query the index
     // Provide the RQL string to the RawQuery method
    .RawQuery<Camera>(@"from index 'Cameras/ByFeatures'
                        select facet(Brand, $p0)")
     // Add the facet options to the "p0" parameter
    .AddParameter("p0", new { PageSize = 3, TermSortMode = FacetTermSortMode.CountDesc })
     // Execute the query
    .ExecuteAggregation();
from index "Cameras/ByFeatures"
select facet(Brand, $p0)
{"p0": { "TermSortMode": "CountDesc", "PageSize": 3 }}

Query results:


// The resulting items will contain:
// =================================

// For the "Brand" Facet:
//     "fuji"    - Count: 4
//     "nikon"   - Count: 3
//     "olympus" - Count: 2

// As requested, only 3 unique items are returned, ordered by documents count descending:
// Get facets results for index-field 'Brand':
// ===========================================
var brandFacets = results["Brand"];
var numberOfBrands = brandFacets.Values.Count; // 3 brands

// Get the aggregated facet value for a specific Brand:
var facetValue = brandFacets.Values[0];
// The brand name is available in the 'Range' property
// Note: value is lower-case since the default RavenDB analyzer was used by the index
Assert.Equal("fuji", facetValue.Range);
// Number of documents for 'Fuji' is available in the 'Count' property
Assert.Equal(4, facetValue.Count);

Facets - Aggregations

Facets definition:


  • Aggregation of data is available for an index-field per unique Facet or Range item.
    For example:

    • Get the total number of UnitsInStock per Brand
    • Get the highest MegaPixels value for documents that cost between 200 & 400
  • The following aggregation operations are available:

    • Sum
    • Average
    • Min
    • Max
  • Multiple operations can be added on each facet, for multiple fields.

// Define the list of facets to query by:
// ======================================
List<FacetBase> facetsWithAggregations = new List<FacetBase>
{
    // Define a Facet:
    // ===============
    new Facet
    {
        FieldName = "Brand",
        Aggregations =
        {
            {
                // Set the aggregation operation:
                FacetAggregation.Sum,
                // Create a HasSet specifying the index-fields for which to perform the aggregation
                new HashSet<FacetAggregationField>
                {
                    // Get total number of UnitsInStock per Brand
                    new FacetAggregationField {Name = "UnitsInStock"}
                }
            },
            {
                FacetAggregation.Average, new HashSet<FacetAggregationField>
                {
                    // Get average Price per Brand
                    new FacetAggregationField {Name = "Price"}
                }
            },
            {
                FacetAggregation.Min, new HashSet<FacetAggregationField>
                {
                    // Get min Price per Brand
                    new FacetAggregationField {Name = "Price"}
                }
            },
            {
                FacetAggregation.Max, new HashSet<FacetAggregationField>
                {
                    // Get max MegaPixels per Brand
                    new FacetAggregationField {Name = "MegaPixels"},
                    // Get max MaxFocalLength per Brand
                    new FacetAggregationField {Name = "MaxFocalLength"}
                }
            }
        }
    },

    // Define a RangeFacet:
    // ====================
    new RangeFacet<Cameras_ByFeatures.IndexEntry>
    {
        Ranges =
        {
            x => x.Price < 200,
            x => x.Price >= 200 && x.Price < 400,
            x => x.Price >= 400 && x.Price < 600,
            x => x.Price >= 600 && x.Price < 800,
            x => x.Price >= 800
        },
        Aggregations =
        {
            {
                FacetAggregation.Sum, new HashSet<FacetAggregationField>
                {
                    // Get total number of UnitsInStock for each group of documents per range specified
                    new FacetAggregationField {Name = "UnitsInStock"}
                }
            },
            {
                FacetAggregation.Average, new HashSet<FacetAggregationField>
                {
                    // Get average Price of each group of documents per range specified
                    new FacetAggregationField {Name = "Price"}
                }
            },
            {
                FacetAggregation.Min, new HashSet<FacetAggregationField>
                {
                    // Get min Price of each group of documents per range specified
                    new FacetAggregationField {Name = "Price"}
                }
            },
            {
                FacetAggregation.Max, new HashSet<FacetAggregationField>
                {
                    // Get max MegaPixels for each group of documents per range specified
                    new FacetAggregationField {Name = "MegaPixels"},
                    // Get max MaxFocalLength for each group of documents per range specified
                    new FacetAggregationField {Name = "MaxFocalLength"}
                }
            }
        }
    }
};

Query the index for facets results:


Dictionary<string, FacetResult> results = session
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facetsWithAggregations)
    .Execute();
Dictionary<string, FacetResult> results = await asyncSession
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facetsWithAggregations)
    .ExecuteAsync();
Dictionary<string, FacetResult> results = session.Advanced
     // Query the index
    .DocumentQuery<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Pass the defined facets from above
    .AggregateBy(facetsWithAggregations)
    .Execute();
Dictionary<string, FacetResult> results = session
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateBy' to aggregate the data by facets
     // Use a builder as follows:
    .AggregateBy(builder => builder
         // Specify an index-field (e.g. 'Brand') for which to get count per unique ITEM
        .ByField(x => x.Brand)
         // Specify the aggregations per the Brand facet:
        .SumOn(x => x.UnitsInStock)
        .AverageOn(x => x.Price)
        .MinOn(x => x.Price)
        .MaxOn(x => x.MegaPixels)
        .MaxOn(x => x.MaxFocalLength))
    .AndAggregateBy(builder => builder
         // Specify ranges within an index field (e.g. 'Price') in order to get count per RANGE
        .ByRanges(
            x => x.Price < 200,
            x => x.Price >= 200 && x.Price < 400,
            x => x.Price >= 400 && x.Price < 600,
            x => x.Price >= 600 && x.Price < 800,
            x => x.Price >= 800)
         // Specify the aggregations per the Price range:
        .SumOn(x => x.UnitsInStock)
        .AverageOn(x => x.Price)
        .MinOn(x => x.Price)
        .MaxOn(x => x.MegaPixels)
        .MaxOn(x => x.MaxFocalLength))
    .Execute();
Dictionary<string, FacetResult> results = session.Advanced
     // Query the index
     // Provide the RQL string to the RawQuery method
    .RawQuery<Camera>(@"from index 'Cameras/ByFeatures'
                        select
                            facet(Brand,
                                  sum(UnitsInStock),
                                  avg(Price),
                                  min(Price),
                                  max(MegaPixels),
                                  max(MaxFocalLength)),
                            facet(Price < $p0,
                                  Price >= $p1 and Price < $p2,
                                  Price >= $p3 and Price < $p4,
                                  Price >= $p5 and Price < $p6,
                                  Price >= $p7,
                                  sum(UnitsInStock),
                                  avg(Price),
                                  min(Price),
                                  max(MegaPixels),
                                  max(MaxFocalLength))")
     // Add the parameters' values
    .AddParameter("p0", 200.0)
    .AddParameter("p1", 200.0)
    .AddParameter("p2", 400.0)
    .AddParameter("p3", 400.0)
    .AddParameter("p4", 600.0)
    .AddParameter("p5", 600.0)
    .AddParameter("p6", 800.0)
    .AddParameter("p7", 800.0)
     // Execute the query
    .ExecuteAggregation();
from index "Cameras/ByFeatures"
select
    facet(Brand,
          sum(UnitsInStock),
          avg(Price),
          min(Price),
          max(MegaPixels),
          max(MaxFocalLength)),
    facet(Price < $p0,
          Price >= $p1 and Price < $p2,
          Price >= $p3 and Price < $p4,
          Price >= $p5 and Price < $p6,
          Price >= $p7,
          sum(UnitsInStock),
          avg(Price),
          min(Price),
          max(MegaPixels),
          max(MaxFocalLength))
{"p0":200.0,"p1":200.0,"p2":400.0,"p3":400.0,"p4":600.0,"p5":600.0,"p6":800.0,"p7":800.0}

Query results:


// The resulting items will contain (Showing partial results):
// ===========================================================

// For the "Brand" Facet:
//     "canon" Count:1, Sum: 30, Name: UnitsInStock
//     "canon" Count:1, Min: 200, Average: 200, Name: Price
//     "canon" Count:1, Max: 30.4, Name: MegaPixels
//     "canon" Count:1, Max: 400, Name: MaxFocalLength
//
//     "fuji" Count:4, Sum: 42, Name: UnitsInStock
//     "fuji" Count:4, Min: 410, Name: Price
//     "fuji" Count:4, Max: 102, Name: MegaPixels
//     "fuji" Count:4, Max: 800, Name: MaxFocalLength
//     
//     etc.....

// For the "Price" Ranges:
//     "Price < 200.0" Count:3, Sum: 17, Name: UnitsInStock
//     "Price < 200.0" Count:3, Min: 100, Average: 133.33, Name: Price
//     "Price < 200.0" Count:3, Max: 32, Name: MegaPixels
//     "Price < 200.0" Count:3, Max: 300, Name: MaxFocalLength
//
//     "Price < 200.0 and Price > 400.0" Count:5, Sum: 75, Name: UnitsInStock
//     "Price < 200.0 and Price > 400.0" Count:5, Min: 200, Average: 252, Name: Price
//     "Price < 200.0 and Price > 400.0" Count:5, Max: 40, Name: MegaPixels
//     "Price < 200.0 and Price > 400.0" Count:5, Max: 600, Name: MaxFocalLength
//     
//     etc.....
// Get results for the 'Brand' Facets:
// ==========================================
var brandFacets = results["Brand"];

// Get the aggregated facet value for a specific Brand:
var facetValue = brandFacets.Values[0];
// The brand name is available in the 'Range' property:
Assert.Equal("canon", facetValue.Range);
// The index-field on which aggregation was done is in the 'Name' property:
Assert.Equal("UnitsInStock", facetValue.Name);
// The requested aggregation result:
Assert.Equal(30, facetValue.Sum);

// Get results for the 'Price' RangeFacets:
// =======================================
var priceRangeFacets = results["Price"];

// Get the aggregated facet value for a specific Brand:
facetValue = priceRangeFacets.Values[0];
// The range string is available in the 'Range' property:
Assert.Equal("Price < 200.0", facetValue.Range);
// The index-field on which aggregation was done is in the 'Name' property:
Assert.Equal("UnitsInStock", facetValue.Name);
// The requested aggregation result:
Assert.Equal(17, facetValue.Sum);

Storing facets definition in a document

Define and store facets in a document:


  • The facets definitions can be stored in a document.

  • That document can then be used by a faceted search query.

// Create a FacetSetup object:
// ===========================
FacetSetup facetSetup = new FacetSetup
{
    // Provide the ID of the document in which the facet setup will be stored.
    // This is optional -
    // if not provided then the session will assign an ID for the stored document.
    Id = "customDocumentID",

    // Define Facets and RangeFacets to query by:
    Facets = new List<Facet> {
        new Facet()
        {
            FieldName = "Brand"
        }},
    
    RangeFacets = new List<RangeFacet>
    {
        new RangeFacet<Cameras_ByFeatures.IndexEntry>
        {
            Ranges =
            {
                x => x.MegaPixels < 20,
                x => x.MegaPixels >= 20 && x.MegaPixels < 30,
                x => x.MegaPixels >= 30 && x.MegaPixels < 50,
                x => x.MegaPixels >= 50
            }
        }
    }
};

// Store the facet setup document and save changes:
// ================================================
session.Store(facetSetup);
session.SaveChanges();

// The document will be stored under the 'FacetSetups' collection

Query using facets from document:


Dictionary<string, FacetResult> results = session
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateUsing'
     // Pass the ID of the document that contains your facets setup
    .AggregateUsing("customDocumentID")
    .Execute();
Dictionary<string, FacetResult> results = await asyncSession
     // Query the index
    .Query<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateUsing'
     // Pass the ID of the document that contains your facets setup
    .AggregateUsing("customDocumentID")
    .ExecuteAsync();
Dictionary<string, FacetResult> results = session.Advanced
     // Query the index
    .DocumentQuery<Cameras_ByFeatures.IndexEntry, Cameras_ByFeatures>()
     // Call 'AggregateUsing'
     // Pass the ID of the document that contains your facets setup
    .AggregateUsing("customDocumentID")
    .Execute();
Dictionary<string, FacetResult> results = session.Advanced
     // Query the index
     // Provide the RQL string to the RawQuery method
    .RawQuery<Camera>(@"from index 'Cameras/ByFeatures'
                        select facet(id('customDocumentID'))")
     // Execute the query
    .ExecuteAggregation();
from index "Cameras/ByFeatures"
select facet(id("customDocumentID"))

Syntax

IAggregationQuery<T> AggregateBy<T>(FacetBase facet);
IAggregationQuery<T> AggregateBy<T>(IEnumerable<FacetBase> facets);
IAggregationQuery<T> AggregateBy<T>(Action<IFacetBuilder<T>> builder);
IAggregationQuery<T> AggregateUsing<T>(string facetSetupDocumentKey);
Parameter Type Description
facet FacetBase FacetBase implementation defining the facet and its options.
Either Facet or RangeFacet.
facets IEnumerable<FacetBase> Enumerable containing FacetBase implementations.
builder Action<IFacetFactory<T>> Builder with a fluent API that constructs a FacetBase instance.
facetSetupDocumentId string ID of a document containing FacetSetup.

public class Facet
{
    public string FieldName { get; set; }
    public FacetOptions Options { get; set; }
}

public class Facet<T>
{
    public Expression<Func<T, object>> FieldName { get; set; }
    public FacetOptions Options { get; set; }
}
public class RangeFacet
{
    public List<string> Ranges { get; set; }
    public FacetOptions Options { get; set; }
}

public class RangeFacet<T>
{
    public List<Expression<Func<T, bool>>> Ranges { get; set; }
    public FacetOptions Options { get; set; }
}
public class FacetBase
{
    public Dictionary<FacetAggregation, HashSet<FacetAggregationField>> Aggregations { get; set; }
    public string DisplayFieldName { get; set; }
}
public enum FacetAggregation
{
    None,
    Max,
    Min,
    Average,
    Sum
}

Fluent API builder methods:

IFacetOperations<T> ByField(string fieldName);
IFacetOperations<T> ByField(Expression<Func<T, object>> path);
IFacetOperations<T> ByRanges(Expression<Func<T, bool>> path, params Expression<Func<T, bool>>[] paths);
IFacetOperations<T> WithDisplayName(string displayName);
IFacetOperations<T> WithOptions(FacetOptions options);
IFacetOperations<T> SumOn(Expression<Func<T, object>> path);
IFacetOperations<T> MinOn(Expression<Func<T, object>> path);
IFacetOperations<T> MaxOn(Expression<Func<T, object>> path);
IFacetOperations<T> AverageOn(Expression<Func<T, object>> path);
Parameter Type Description
fieldName string The index-field to use for the facet
path Expression<Func<T, bool>> Points to the index-field to use for the facet (ByRanges, ByField) or for the aggregation (SumOn, MinOn, MaxOn, AverageOn)
displayName string If set, results of a facet will be returned under this name
options FacetOptions Non-default options to use in the facet definition

Options:

public class FacetOptions
{
    public FacetTermSortMode TermSortMode { get; set; } = FacetTermSortMode.ValueAsc;
    public bool IncludeRemainingTerms { get; set; }
    public int Start { get; set; }
    public int PageSize { get; set; } = int.MaxValue;
}
Option Type Description
TermSortMode FacetTermSortMode Set the sort order on the resulting items
(ValueAsc (Default), ValueDesc, CountAsc, CountDesc)
Start int The position from which to send items (how many to skip)
PageSize int Number of items to return
IncludeRemainingTerms bool Indicates if remaining terms that didn't make it into the requested PageSize should be included in results