Session: Querying: How to Perform a Faceted (Aggregated) Search

To execute facet (aggregation) query using the session Query method, use the AggregateBy or AggregateUsing methods. This will scope you to the aggregation query builder where you will be allowed to define single or multiple facets for the query using a straightforward and fluent API.

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);
Parameters
facet FacetBase FacetBase implementation defining the scope of 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

Facet & RangeFacet

Facet vs RangeFacet

RangeFacet allows you to split the results of the calculations into several ranges, in contrast to Facet where whole spectrum of results will be used to generate a single outcome.

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

    public FacetOptions Options { get; set; }

    public Dictionary<FacetAggregation, HashSet<FacetAggregationField>> Aggregations { get; set; }

    public string DisplayFieldName { get; set; }
}

public class Facet<T>
{
    public Expression<Func<T, object>> FieldName { get; set; }

    public FacetOptions Options { get; set; }

    public Dictionary<FacetAggregation, HashSet<FacetAggregationField>> Aggregations { get; set; }

    public string DisplayFieldName { get; set; }
}
public class RangeFacet
{
    public List<string> Ranges { get; set; }

    public FacetOptions Options { get; set; }

    public Dictionary<FacetAggregation, HashSet<FacetAggregationField>> Aggregations { get; set; }

    public string DisplayFieldName { get; set; }
}

public class RangeFacet<T>
{
    public List<Expression<Func<T, bool>>> Ranges { get; set; }

    public FacetOptions Options { get; set; }

    public Dictionary<FacetAggregation, HashSet<FacetAggregationField>> Aggregations { get; set; }

    public string DisplayFieldName { get; set; }
}
public enum FacetAggregation
{
    None,
    Max,
    Min,
    Average,
    Sum
}

Builder

IFacetOperations<T> ByRanges(Expression<Func<T, bool>> path, params Expression<Func<T, bool>>[] paths);

IFacetOperations<T> ByField(Expression<Func<T, object>> path);

IFacetOperations<T> ByField(string fieldName);

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);
Parameters
path string Points to the index field that should be used for operation (ByRanges, ByField) or to document field that should be used for aggregation (SumOn, MinOn, MaxOn, AverageOn)
fieldName string Points to the index field that should be used for operation (ByRanges, ByField) or to document field that should be used for aggregation (SumOn, MinOn, MaxOn, AverageOn)
displayName string If set, results of a facet will be returned under this name
options FacetOptions Non-default options that should be used for operation

Calculation Operations

SumOn(),MinOn(),MinOn(), or AverageOn() add a calculation on numerical fields to each facet. For example, AverageOn(x => x.Cost) adds to each facet the sum of the costs of all the results in that facet. See example below. These calculation operations can take any numerical field in the results of the query. Multiple of these operations can be added to each facet clause, and can be called for multiple fields.

Options

public FacetTermSortMode TermSortMode { get; set; } = FacetTermSortMode.ValueAsc;

public bool IncludeRemainingTerms { get; set; }

public int Start { get; set; }

public int PageSize { get; set; } = int.MaxValue;
Options
TermSortMode FacetTermSortMode Indicates how terms should be sorted (ValueAsc, ValueDesc, CountAsc, CountDesc)
IncludeRemainingTerms bool Indicates if remaining terms should be included in results
Start int Used to skip given number of facet results in the outcome
PageSize int Used to limit facet results to the given value

Example I

Query using Facet and RangeFacet:

Dictionary<string, FacetResult> facets = session
    .Query<Camera>("Camera/Costs")
    .AggregateBy(new Facet
    {
        FieldName = "Manufacturer",
        Options = new FacetOptions
        {
            TermSortMode = FacetTermSortMode.CountDesc
        }
    })
    .AndAggregateBy(new RangeFacet<Camera>
    {
        Ranges =
        {
            camera => camera.Cost < 200m,
            camera => camera.Cost >= 200m && camera.Cost < 400m,
            camera => camera.Cost >= 400m && camera.Cost < 600m,
            camera => camera.Cost >= 600m && camera.Cost < 800m,
            camera => camera.Cost >= 800m
        },
        Aggregations =
        {
            {
                FacetAggregation.Average,
                new HashSet<FacetAggregationField> { new FacetAggregationField { Name = "Cost" } }
            }
        }
    })
    .AndAggregateBy(new RangeFacet<Camera>
    {
        Ranges =
        {
            camera => camera.Megapixels < 3.0,
            camera => camera.Megapixels >= 3.0 && camera.Megapixels < 7.0,
            camera => camera.Megapixels >= 7.0 && camera.Megapixels < 10.0,
            camera => camera.Megapixels >= 10.0
        }
    })
    .Execute();
Dictionary<string, FacetResult> facets = await asyncSession
    .Query<Camera>("Camera/Costs")
    .AggregateBy(new Facet
    {
        FieldName = "Manufacturer",
        Options = new FacetOptions
        {
            TermSortMode = FacetTermSortMode.CountDesc
        }
    })
    .AndAggregateBy(new RangeFacet<Camera>
    {
        Ranges =
        {
            camera => camera.Cost < 200m,
            camera => camera.Cost >= 200m && camera.Cost < 400m,
            camera => camera.Cost >= 400m && camera.Cost < 600m,
            camera => camera.Cost >= 600m && camera.Cost < 800m,
            camera => camera.Cost >= 800m
        },
        Aggregations =
        {
            {
                FacetAggregation.Average,
                new HashSet<FacetAggregationField> { new FacetAggregationField { Name = "Cost" } }
            }
        }
    })
    .AndAggregateBy(new RangeFacet<Camera>
    {
        Ranges =
        {
            camera => camera.Megapixels < 3.0,
            camera => camera.Megapixels >= 3.0 && camera.Megapixels < 7.0,
            camera => camera.Megapixels >= 7.0 && camera.Megapixels < 10.0,
            camera => camera.Megapixels >= 10.0
        }
    })
    .ExecuteAsync();
from index 'Camera/Costs' 
select 
facet(Manufacturer), 
facet(Cost < 200, 
      Cost >= 200 AND Cost < 400, 
      Cost >= 400 AND Cost < 600, 
      Cost >= 600 AND Cost < 800, 
      Cost >= 800),
facet(Megapixels < 3, 
      Megapixels >= 3 AND Megapixels < 7, 
      Megapixels >= 7 AND Megapixels < 10, 
      Megapixels >= 10)

Example II

Query using builder:

Dictionary<string, FacetResult> facets = session
    .Query<Camera>("Camera/Costs")
    .AggregateBy(builder => builder
        .ByField(x => x.Manufacturer)
        .WithOptions(new FacetOptions
        {
            TermSortMode = FacetTermSortMode.CountDesc
        }))
    .AndAggregateBy(builder => builder
        .ByRanges(
            camera => camera.Cost < 200m,
            camera => camera.Cost >= 200m && camera.Cost < 400m,
            camera => camera.Cost >= 400m && camera.Cost < 600m,
            camera => camera.Cost >= 600m && camera.Cost < 800m,
            camera => camera.Cost >= 800m)
        .AverageOn(x => x.Cost))
    .AndAggregateBy(builder => builder
        .ByRanges(
            camera => camera.Megapixels < 3.0,
            camera => camera.Megapixels >= 3.0 && camera.Megapixels < 7.0,
            camera => camera.Megapixels >= 7.0 && camera.Megapixels < 10.0,
            camera => camera.Megapixels >= 10.0))
    .Execute();
Dictionary<string, FacetResult> facets = await asyncSession
    .Query<Camera>("Camera/Costs")
    .AggregateBy(builder => builder
        .ByField(x => x.Manufacturer)
        .WithOptions(new FacetOptions
        {
            TermSortMode = FacetTermSortMode.CountDesc
        }))
    .AndAggregateBy(builder => builder
        .ByRanges(
            camera => camera.Cost < 200m,
            camera => camera.Cost >= 200m && camera.Cost < 400m,
            camera => camera.Cost >= 400m && camera.Cost < 600m,
            camera => camera.Cost >= 600m && camera.Cost < 800m,
            camera => camera.Cost >= 800m)
        .AverageOn(x => x.Cost))
    .AndAggregateBy(builder => builder
        .ByRanges(
            camera => camera.Megapixels < 3.0,
            camera => camera.Megapixels >= 3.0 && camera.Megapixels < 7.0,
            camera => camera.Megapixels >= 7.0 && camera.Megapixels < 10.0,
            camera => camera.Megapixels >= 10.0))
    .ExecuteAsync();
from index 'Camera/Costs' 
select 
facet(Manufacturer), 
facet(Cost < 200, 
      Cost >= 200 AND Cost < 400, 
      Cost >= 400 AND Cost < 600, 
      Cost >= 600 AND Cost < 800, 
      Cost >= 800,
      average(Cost)),
facet(Megapixels < 3, 
      Megapixels >= 3 AND Megapixels < 7, 
      Megapixels >= 7 AND Megapixels < 10, 
      Megapixels >= 10)

Example III

session.Store(new FacetSetup
{
    Facets = new List<Facet>
        {
            new Facet
            {
                FieldName = "Manufacturer"
            }
        },
    RangeFacets = new List<RangeFacet>
        {
            new RangeFacet<Camera>
            {
                Ranges =
                {
                    camera => camera.Cost < 200m,
                    camera => camera.Cost >= 200m && camera.Cost < 400m,
                    camera => camera.Cost >= 400m && camera.Cost < 600m,
                    camera => camera.Cost >= 600m && camera.Cost < 800m,
                    camera => camera.Cost >= 800m
                }
            },
            new RangeFacet<Camera>
            {
                Ranges =
                {
                    camera => camera.Megapixels < 3.0,
                    camera => camera.Megapixels >= 3.0 && camera.Megapixels < 7.0,
                    camera => camera.Megapixels >= 7.0 && camera.Megapixels < 10.0,
                    camera => camera.Megapixels >= 10.0
                }
            }
        }
}, "facets/CameraFacets");

session.SaveChanges();

Dictionary<string, FacetResult> facets = session
    .Query<Camera>("Camera/Costs")
    .AggregateUsing("facets/CameraFacets")
    .Execute();
await asyncSession.StoreAsync(new FacetSetup
{
    Facets = new List<Facet>
        {
            new Facet
            {
                FieldName = "Manufacturer"
            }
        },
    RangeFacets = new List<RangeFacet>
        {
            new RangeFacet<Camera>
            {
                Ranges =
                {
                    camera => camera.Cost < 200m,
                    camera => camera.Cost >= 200m && camera.Cost < 400m,
                    camera => camera.Cost >= 400m && camera.Cost < 600m,
                    camera => camera.Cost >= 600m && camera.Cost < 800m,
                    camera => camera.Cost >= 800m
                }
            },
            new RangeFacet<Camera>
            {
                Ranges =
                {
                    camera => camera.Megapixels < 3.0,
                    camera => camera.Megapixels >= 3.0 && camera.Megapixels < 7.0,
                    camera => camera.Megapixels >= 7.0 && camera.Megapixels < 10.0,
                    camera => camera.Megapixels >= 10.0
                }
            }
        }
}, "facets/CameraFacets");

await asyncSession.SaveChangesAsync();

Dictionary<string, FacetResult> facets = await asyncSession
    .Query<Camera>("Camera/Costs")
    .AggregateUsing("facets/CameraFacets")
    .ExecuteAsync();
from index 'Camera/Costs' 
select facet(id('facets/CameraFacets'))

Example IV

Demonstrates how calculation operations can be added to facet queries. Multiple operations can be added on each facet, for multiple fields.

Dictionary<string, FacetResult> facets = session
    .Query<Camera>("Camera/Costs")
    .AggregateBy(builder => builder
        .ByField(x => x.Manufacturer)
        .MinOn(x => x.Cost)
        .MaxOn(x => x.Megapixels)
        .MaxOn(x => x.Zoom))
    .AndAggregateBy(builder => builder
        .ByRanges(
            camera => camera.Cost < 400m,
            camera => camera.Cost >= 400m)
        .AverageOn(x => x.Cost)
        .MaxOn(x => x.Cost)
        .MinOn(x => x.Cost))
    .Execute();
Dictionary<string, FacetResult> facets = await asyncSession
    .Query<Camera>("Camera/Costs")
    .AggregateBy(builder => builder
        .ByField(x => x.Manufacturer)
        .MinOn(x => x.Cost)
        .MaxOn(x => x.Megapixels)
        .MaxOn(x => x.Zoom))
    .AndAggregateBy(builder => builder
        .ByRanges(
            camera => camera.Cost < 400m,
            camera => camera.Cost >= 400m)
        .AverageOn(x => x.Cost)
        .MaxOn(x => x.Cost)
        .MinOn(x => x.Cost))
    .ExecuteAsync();
from index 'Camera/Costs'
select
facet(Manufacturer,
      min(Cost),
      max(Megapixels),
      max(Zoom)),
facet(Cost < 400,
      Cost >= 400,
      avg(Cost),
      max(Cost),
      min(Cost))