foundatio-repositories

star 2.5k

Use this skill when querying, counting, patching, or paginating data through Foundatio.Repositories Elasticsearch abstractions. Covers filter expressions, aggregation queries, partial and script patches, and search-after pagination. Apply when working with normal query/count/patch/pagination repository code; reserve raw IElasticClient for migrations or index maintenance that cannot be expressed through repositories.

exceptionless By exceptionless schedule Updated 6/7/2026

name: foundatio-repositories description: > Use this skill when querying, counting, patching, or paginating data through Foundatio.Repositories Elasticsearch abstractions. Covers filter expressions, aggregation queries, partial and script patches, and search-after pagination. Apply when working with normal query/count/patch/pagination repository code; reserve raw IElasticClient for migrations or index maintenance that cannot be expressed through repositories.


Foundatio Repositories

Foundatio.Repositories provides a high-level Elasticsearch abstraction. Use repository methods for normal query, count, patch, and pagination work. Reserve raw IElasticClient for migrations or index maintenance that cannot be expressed through repositories.

Documentation: https://repositories.foundatio.dev / https://parsers.foundatio.dev

Repository Hierarchy

IRepository<T>                             — CRUD, Patch, Remove
  └─ ISearchableRepository<T>              — FindAsync, CountAsync, aggregations
       └─ IRepositoryOwnedByOrganization<T>
            └─ IRepositoryOwnedByProject<T>
                 └─ IRepositoryOwnedByOrganizationAndProject<T>
Interface Entity Index Type
IEventRepository PersistentEvent DailyIndex (date-partitioned)
IStackRepository Stack VersionedIndex (single index)
IProjectRepository Project VersionedIndex
IOrganizationRepository Organization VersionedIndex
IUserRepository User VersionedIndex
ITokenRepository Token VersionedIndex
IMigrationStateRepository MigrationState VersionedIndex

Important: .Index(start, end) only routes to correct daily shards for DailyIndex (events). It is a no-op for VersionedIndex.

CountAsync + AggregationsExpression

CountAsync returns a CountResult with .Total (long) and .Aggregations (AggregationsHelper).

AggregationsExpression DSL

Expression Meaning
cardinality:field Distinct count
terms:field Terms aggregation
terms:(field~SIZE) Terms with bucket size limit
terms:(field~SIZE sub_agg) Terms with nested aggregation
terms:(field @include:VALUE) Terms with include filter
date:field Date histogram (auto interval)
date:field~1d Date histogram, daily interval
date:field~1M Date histogram, monthly interval
date:(field sub_agg) Date histogram with nested agg
sum:field~DEFAULT Sum with default value
min:field / max:field Min/Max aggregation
avg:field Average aggregation
-sum:field~1 Sort descending by this agg (prefix -)

Multiple aggregations are space-separated: "cardinality:stack_id terms:type sum:count~1"

Accessing Aggregation Results

Naming convention: {type}_{field} — the aggregation type prefix + underscore + field name.

result.Aggregations.Cardinality("cardinality_stack_id").Value
result.Aggregations.Terms<string>("terms_type").Buckets       // .Key, .Total
result.Aggregations.DateHistogram("date_date").Buckets        // .Date, .Total
result.Aggregations.Sum("sum_count").Value
result.Aggregations.Min<DateTime>("min_date").Value
result.Aggregations.Max<DateTime>("max_date").Value
result.Aggregations.Average("avg_value").Value

// Nested aggs inside buckets
foreach (var bucket in result.Aggregations.Terms<string>("terms_stack_id").Buckets)
    bucket.Aggregations.Cardinality("cardinality_user").Value;

FilterExpression (Lucene-style)

FilterExpression accepts Lucene query syntax parsed by Foundatio Parsers:

.FilterExpression("type:error (status:open OR status:regressed)")
.FilterExpression($"project:{projectId}")
.FilterExpression($"stack:{stackId}")
.FilterExpression($"signature_hash:{signature}")
.FilterExpression("is_deleted:false")

Building OR filters from collections:

string filter = String.Join(" OR ", stackIds.Select(id => $"stack:{id}"));

Query Extension Methods

Method Purpose File
.Organization(id) Filter by organization_id OrganizationQuery.cs
.Organization(ids) Filter by multiple org IDs OrganizationQuery.cs
.Project(id) Filter by project_id ProjectQuery.cs
.Stack(id) / .Stack(ids) Filter by stack_id StackQuery.cs
.ExcludeStack(id) Exclude stack_id StackQuery.cs
.AppFilter(sf) Apply app-level system filter AppFilterQuery.cs
.SystemFilter(query) Chain a pre-built query Foundatio built-in
.EnforceEventStackFilter() Resolve stack filters to event IDs EventStackFilterQuery.cs
.DateRange(start, end, field) Date range filter Foundatio built-in
.Index(start, end) Route to daily shards (events only) Foundatio built-in
.FieldEquals(expr, value) Exact field match Foundatio built-in
.SortExpression(sort) Sort expression Foundatio built-in

Pagination

Use SearchAfterPaging() for deep pagination (never offset-based). NextPageAsync() returns Task<bool> and mutates results in-place.

var results = await _repository.GetAllAsync(o => o.SearchAfterPaging().PageLimit(500));
do
{
    foreach (var doc in results.Documents)
    {
        // process document
    }
} while (!cancellationToken.IsCancellationRequested && await results.NextPageAsync());

Key rules:

  • Never use while(true) { ... break; } — use do/while or while(condition)
  • Always check CancellationToken in the loop condition

PatchAllAsync / PatchAsync

Use PartialPatch for field-level updates, ScriptPatch for Painless scripts. Pass o => o.ImmediateConsistency() when write-then-read consistency is needed (tests). Use o => o.Notifications(false) to suppress change notifications.

await _tokenRepository.PatchAllAsync(
    q => q.Organization(orgId).FieldEquals(t => t.IsSuspended, false),
    new PartialPatch(new { is_suspended = true }),
    o => o.ImmediateConsistency());

Anti-Patterns

Avoid these in normal repository/query work:

  • Use _elasticClient.SearchAsync<T>(...) — use CountAsync or FindAsync
  • Use _elasticClient.MultiGetAsync(...) — use GetByIdsAsync
  • Use _elasticClient.DeleteByQueryAsync<T>(...) — use RemoveAllAsync
  • Use _elasticClient.UpdateByQueryAsync<T>(...) — use PatchAllAsync
  • Use _elasticClient.Indices.RefreshAsync(...) — use o => o.ImmediateConsistency()
  • Use while(true) { ... break; } for pagination — use do/while or while(condition)
Install via CLI
npx skills add https://github.com/exceptionless/Exceptionless --skill foundatio-repositories
Repository Details
star Stars 2,453
call_split Forks 506
navigation Branch main
article Path SKILL.md
More from Creator
exceptionless
exceptionless Explore all skills →