Class CosmosRepository<T>
Basic implementation of a Cosmos DB repository. Provides basic CRUD operations for a Cosmos DB entity, manages the container instance, and provides common functionality for custom queries as protected values and methods.
Implements
Inherited Members
Namespace: Benday.CosmosDb.Repositories
Assembly: Benday.CosmosDb.dll
Syntax
public abstract class CosmosRepository<T> : IRepository<T> where T : class, ICosmosIdentity, new()
Type Parameters
| Name | Description |
|---|---|
| T | Domain model type managed by this repository |
Remarks
The library supports two complementary ways to capture query diagnostics, and both fire for every event — using one does not disable the other:
- Per-repository customization via overriding OnQueryDiagnostics(CosmosQueryDiagnostics). Use this when a specific repository needs non-default handling of its own events.
-
App-wide structured diagnostics via implementing and registering
an ICosmosQueryLogSink in DI. Use this for cross-cutting
behavior like structured log files, test captures, or production
observability pipelines. A default NoOpCosmosQueryLogSink
is registered automatically; consumers override it by calling
CosmosRegistrationHelper.WithQueryLogSinkor by registering their own implementation in the service collection beforeAddCosmosDb.
Diagnostic events are distinguished by EventKind — point operations, per-page feed responses, and query totals. Neither channel affects ILogger output, which fires unchanged regardless of how diagnostics are routed.
Constructors
| Edit this page View SourceCosmosRepository(IOptions<CosmosRepositoryOptions<T>>, CosmosClient, ILogger, ICosmosQueryLogSink?)
Constructor for the repository.
Declaration
public CosmosRepository(IOptions<CosmosRepositoryOptions<T>> options, CosmosClient client, ILogger logger, ICosmosQueryLogSink? sink = null)
Parameters
| Type | Name | Description |
|---|---|---|
| IOptions<CosmosRepositoryOptions<T>> | options | Configuration options |
| CosmosClient | client | Cosmos Db client instance. NOTE: for performance reasons, this should probably be a singleton in the application. |
| ILogger | logger | Logger instance. |
| ICosmosQueryLogSink | sink | Optional app-wide diagnostics sink. When |
Exceptions
| Type | Condition |
|---|---|
| ArgumentException |
Fields
| Edit this page View Source_Options
Declaration
protected readonly CosmosRepositoryOptions<T> _Options
Field Value
| Type | Description |
|---|---|
| CosmosRepositoryOptions<T> |
_sink
Sink that receives every CosmosQueryDiagnostics event the repository emits. Defaults to Instance when no sink is provided via constructor injection.
Declaration
protected readonly ICosmosQueryLogSink _sink
Field Value
| Type | Description |
|---|---|
| ICosmosQueryLogSink |
Properties
| Edit this page View SourceBatchSize
Batch size for saving items to the Cosmos DB container. This is used to limit the number of items saved in a single batch. Default is 50 items per batch.
Declaration
protected virtual int BatchSize { get; set; }
Property Value
| Type | Description |
|---|---|
| int |
EntityType
Get the entity type value for this repository. By default this is the class name for the domain model type managed by this repository.
Declaration
public virtual string EntityType { get; }
Property Value
| Type | Description |
|---|---|
| string |
Logger
Declaration
protected ILogger Logger { get; }
Property Value
| Type | Description |
|---|---|
| ILogger |
Methods
| Edit this page View SourceAfterSaveBatch(TransactionalBatchResponse, T[], int, int)
Declaration
protected virtual Task AfterSaveBatch(TransactionalBatchResponse response, T[] batch, int currentBatch, int batchCount)
Parameters
| Type | Name | Description |
|---|---|---|
| TransactionalBatchResponse | response | |
| T[] | batch | |
| int | currentBatch | |
| int | batchCount |
Returns
| Type | Description |
|---|---|
| Task |
BeforeSaveBatch(TransactionalBatch, T[], int, int)
Declaration
protected virtual Task BeforeSaveBatch(TransactionalBatch cosmosBatch, T[] batch, int currentBatch, int batchCount)
Parameters
| Type | Name | Description |
|---|---|---|
| TransactionalBatch | cosmosBatch | |
| T[] | batch | |
| int | currentBatch | |
| int | batchCount |
Returns
| Type | Description |
|---|---|
| Task |
DeleteAsync(string)
Delete an item from the Cosmos DB container.
Declaration
public Task DeleteAsync(string id)
Parameters
| Type | Name | Description |
|---|---|---|
| string | id | Id of the item |
Returns
| Type | Description |
|---|---|
| Task |
Exceptions
| Type | Condition |
|---|---|
| InvalidOperationException |
ExecuteScalarAsync<TResult>(IQueryable<T>, Func<IQueryable<T>, Task<Response<TResult>>>, string, PartitionKey, Func<TResult, int>?)
Executes a scalar Cosmos SDK LINQ operation (such as CountAsync,
FirstOrDefaultAsync, MaxAsync) through the library's
diagnostics pipeline. The SDK's extension methods on IQueryable
bypass this pipeline when called directly; this helper lets you call
them while still capturing request charge, timing, query text, and
cross-partition warnings in the same format as list queries.
Declaration
protected Task<TResult> ExecuteScalarAsync<TResult>(IQueryable<T> query, Func<IQueryable<T>, Task<Response<TResult>>> operation, string queryDescription, PartitionKey partitionKey, Func<TResult, int>? resultCountSelector = null)
Parameters
| Type | Name | Description |
|---|---|---|
| IQueryable<T> | query | The IQueryable<T> against which to execute the operation. |
| Func<IQueryable<T>, Task<Response<TResult>>> | operation | A delegate that invokes the desired SDK extension method on the queryable and returns its Response<T>. |
| string | queryDescription | Logging description for the query. |
| PartitionKey | partitionKey | Partition key scope for the query. |
| Func<TResult, int> | resultCountSelector | Optional: a delegate that maps the scalar result to an integer count for diagnostics purposes. Defaults to 1 if the result is non-null or a value type; 0 if the result is null. For most scalar operations the default is fine. |
Returns
| Type | Description |
|---|---|
| Task<TResult> | The resource value from the SDK's Response<T>. |
Type Parameters
| Name | Description |
|---|---|
| TResult | The return type of the SDK operation. |
Remarks
The SQL text for diagnostics is extracted from the query
via ToQueryDefinition<T>(IQueryable<T>). For some
queryable shapes this extraction can fail; when it does, the query
still executes normally and all other diagnostics fields
(request charge, duration, result count, cross-partition) are
populated. The QueryText field
on the resulting event will be null in that case.
Examples
public async Task<int> GetCountAsync(string tenantId)
{
var queryContext = await GetQueryContextAsync(tenantId);
return await ExecuteScalarAsync(
queryContext.Queryable,
q => q.CountAsync(),
GetQueryDescription(nameof(GetCountAsync)),
queryContext.PartitionKey);
}
See Also
| Edit this page View SourceGetAllAsync()
Get all items in the repository. NOTE: this almost certainly performs a cross-partition query and should be used with caution.
Declaration
public Task<IEnumerable<T>> GetAllAsync()
Returns
| Type | Description |
|---|---|
| Task<IEnumerable<T>> | The matching items |
GetByIdAsync(string)
Get an item by its id. This method will return null if the item is not found. NOTE: this almost certainly performs a cross-partition query and should be used with caution because it does not use a partition key.
Declaration
public Task<T?> GetByIdAsync(string id)
Parameters
| Type | Name | Description |
|---|---|---|
| string | id |
Returns
| Type | Description |
|---|---|
| Task<T> | The first matching entity |
GetContainerAsync()
Get the container instance. This method will initialize the container if it is null.
Declaration
protected Task<Container> GetContainerAsync()
Returns
| Type | Description |
|---|---|
| Task<Container> | Reference to the container |
Exceptions
| Type | Condition |
|---|---|
| InvalidOperationException |
GetPagedAsync(int, string?)
Gets a page of results with continuation support for efficient large result set retrieval.
Declaration
[Obsolete("This overload performs a cross-partition query without a partition key. Use GetPagedAsync(string tenantId, ...) instead unless you explicitly need a cross-partition scan.")]
public virtual Task<PagedResults<T>> GetPagedAsync(int pageSize = 100, string? continuationToken = null)
Parameters
| Type | Name | Description |
|---|---|---|
| int | pageSize | Maximum number of items to return in this page |
| string | continuationToken | Continuation token from previous query (null for first page) |
Returns
| Type | Description |
|---|---|
| Task<PagedResults<T>> | A page of results with continuation information |
GetPagedAsync(string, int, string?)
Gets a page of results for a specific partition with continuation support.
Declaration
protected virtual Task<PagedResults<T>> GetPagedAsync(string tenantId, int pageSize = 100, string? continuationToken = null)
Parameters
| Type | Name | Description |
|---|---|---|
| string | tenantId | Value to use for the first-level partition key (tenant id) |
| int | pageSize | Maximum number of items to return in this page |
| string | continuationToken | Continuation token from previous query (null for first page) |
Returns
| Type | Description |
|---|---|
| Task<PagedResults<T>> | A page of results with continuation information |
GetPartitionKey(string, string)
Get the partition key for an item.
Declaration
protected virtual PartitionKey GetPartitionKey(string tenantId, string entityType)
Parameters
| Type | Name | Description |
|---|---|---|
| string | tenantId | Top-level partition key value (tenant id) |
| string | entityType | Second-level partition key value (entity type) |
Returns
| Type | Description |
|---|---|
| PartitionKey |
GetPartitionKey(T)
Get the partition key for an item.
Declaration
protected virtual PartitionKey GetPartitionKey(T item)
Parameters
| Type | Name | Description |
|---|---|---|
| T | item |
Returns
| Type | Description |
|---|---|
| PartitionKey |
GetQueryContextAsync()
Creates a query context for the repository WITHOUT a partition key, resulting in a cross-partition query. Prefer the overload that accepts a tenantId for partition-scoped queries.
Declaration
[Obsolete("This overload performs a cross-partition query without a partition key. Use GetQueryContextAsync(string tenantId) instead unless you explicitly need a cross-partition scan.")]
protected virtual Task<QueryContext<T>> GetQueryContextAsync()
Returns
| Type | Description |
|---|---|
| Task<QueryContext<T>> | A query context containing the LINQ queryable with an empty partition key. |
Exceptions
| Type | Condition |
|---|---|
| InvalidOperationException | Thrown when the underlying LINQ queryable cannot be created. |
GetQueryContextAsync(string)
Creates a query context for the repository with the specified partition key configuration. This is the starting point for all custom LINQ queries built off of this repository by child repository classes.
Declaration
protected virtual Task<QueryContext<T>> GetQueryContextAsync(string tenantId)
Parameters
| Type | Name | Description |
|---|---|---|
| string | tenantId | Value to use for the first-level partition key (tenant id). |
Returns
| Type | Description |
|---|---|
| Task<QueryContext<T>> | A query context containing the LINQ queryable and its configured partition key. |
GetQueryContextAsync(string, string)
Creates a query context for the repository with the specified partition key configuration. This is the starting point for all custom LINQ queries built off of this repository by child repository classes.
Declaration
protected virtual Task<QueryContext<T>> GetQueryContextAsync(string tenantId, string entityType)
Parameters
| Type | Name | Description |
|---|---|---|
| string | tenantId | Value to use for the first-level partition key (tenant id). |
| string | entityType | Entity type value for the second-level partition key. |
Returns
| Type | Description |
|---|---|
| Task<QueryContext<T>> | A query context containing the LINQ queryable and its configured partition key. |
GetQueryDescription(string)
Gets a description for a query. By default, this will return the type name of the repository and the method name. By default, detect and use the method name of the caller.
Declaration
protected string GetQueryDescription(string methodName = "")
Parameters
| Type | Name | Description |
|---|---|---|
| string | methodName | Method that's calling the query |
Returns
| Type | Description |
|---|---|
| string |
GetQueryDescription(string, string)
Gets a description for a query. By default, this will return the type name of the repository and the method name as a formatted string.
Declaration
protected string GetQueryDescription(string typeName, string methodName)
Parameters
| Type | Name | Description |
|---|---|---|
| string | typeName | Name of the type |
| string | methodName | Name of the method |
Returns
| Type | Description |
|---|---|
| string | Formatted query description string |
GetResultsAsync(QueryDefinition, string, PartitionKey)
Convenience overload of GetResultsAsync<TResult>(QueryDefinition, string, PartitionKey)
for the common case where the result type is the repository's own
entity type T.
Declaration
protected Task<List<T>> GetResultsAsync(QueryDefinition query, string queryDescription, PartitionKey partitionKey)
Parameters
| Type | Name | Description |
|---|---|---|
| QueryDefinition | query | The parameterized Cosmos SQL query to run. |
| string | queryDescription | Logging description for the query. |
| PartitionKey | partitionKey | Partition key scope for the query. |
Returns
| Type | Description |
|---|---|
| Task<List<T>> | All matching items. |
GetResultsAsync<TResult>(FeedIterator<TResult>, string, string?, IReadOnlyDictionary<string, object?>?, PartitionKey)
Reads all pages from a feed iterator, timing each page, accumulating totals, and emitting per-page and total diagnostics through OnQueryDiagnostics(CosmosQueryDiagnostics).
Declaration
protected Task<List<TResult>> GetResultsAsync<TResult>(FeedIterator<TResult> resultSetIterator, string queryDescription, string? queryText = null, IReadOnlyDictionary<string, object?>? parameters = null, PartitionKey partitionKey = default)
Parameters
| Type | Name | Description |
|---|---|---|
| FeedIterator<TResult> | resultSetIterator | Feed iterator to read the results from. |
| string | queryDescription | Description of this query for logging. |
| string | queryText | The generated SQL text (for diagnostics). Optional. |
| IReadOnlyDictionary<string, object> | parameters | Query parameters (for diagnostics). Optional. |
| PartitionKey | partitionKey | Partition key scope for the query (for diagnostics). |
Returns
| Type | Description |
|---|---|
| Task<List<TResult>> | All items from the iterator. |
Type Parameters
| Name | Description |
|---|---|
| TResult | Type of item produced by the feed iterator. Typically |
GetResultsAsync<TResult>(QueryDefinition, string, PartitionKey)
Executes a raw Cosmos SQL query and returns all results, logging the same diagnostics as the LINQ overload so raw-SQL queries and LINQ queries show up symmetrically in structured logs.
Declaration
protected Task<List<TResult>> GetResultsAsync<TResult>(QueryDefinition query, string queryDescription, PartitionKey partitionKey)
Parameters
| Type | Name | Description |
|---|---|---|
| QueryDefinition | query | The parameterized Cosmos SQL query to run. |
| string | queryDescription | Logging description for the query. |
| PartitionKey | partitionKey | Partition key scope for the query. |
Returns
| Type | Description |
|---|---|
| Task<List<TResult>> | All matching items. |
Type Parameters
| Name | Description |
|---|---|
| TResult | Type of item returned by the query. Typically |
Remarks
Use this when a query can't be expressed in LINQ — cross-apply joins over nested arrays, dynamic EXISTS clauses, VectorDistance, conditional aggregation, etc. The library's LINQ support handles the common case; this overload covers the cases it can't.
See Also
| Edit this page View SourceGetResultsAsync<TResult>(IQueryable<TResult>, string, PartitionKey)
Executes a LINQ query and returns all results, logging per-page and total diagnostics through OnQueryDiagnostics(CosmosQueryDiagnostics).
Declaration
protected Task<List<TResult>> GetResultsAsync<TResult>(IQueryable<TResult> query, string queryDescription, PartitionKey partitionKey)
Parameters
| Type | Name | Description |
|---|---|---|
| IQueryable<TResult> | query | query to run |
| string | queryDescription | logging description for the query |
| PartitionKey | partitionKey | partition key that's configured for this query. NOTE: this is purely to logging purposes |
Returns
| Type | Description |
|---|---|
| Task<List<TResult>> | All matching items. |
Type Parameters
| Name | Description |
|---|---|
| TResult | Type of item returned by the query. Typically |
See Also
Initialize()
Initializes the repository. This method will create the database and container if they don't already exist.
Declaration
protected Task Initialize()
Returns
| Type | Description |
|---|---|
| Task |
IsCrossPartitionQuery(CosmosDiagnostics)
Attempt to determine if a query is a cross-partition query based on the diagnostics.
Declaration
protected virtual bool IsCrossPartitionQuery(CosmosDiagnostics diagnostics)
Parameters
| Type | Name | Description |
|---|---|---|
| CosmosDiagnostics | diagnostics | Diagnostics for a query response |
Returns
| Type | Description |
|---|---|
| bool | True if it detects a cross-partition query. |
LogFeedResponseDiagnostics(string, double, CosmosDiagnostics, TimeSpan, int, string?, IReadOnlyDictionary<string, object?>?, PartitionKey)
Logs diagnostics for a single page of feed iterator results (including cross-partition query detection) and fires OnQueryDiagnostics(CosmosQueryDiagnostics) with a FeedResponsePage event.
Declaration
protected bool LogFeedResponseDiagnostics(string queryDescription, double requestCharge, CosmosDiagnostics diagnostics, TimeSpan duration, int resultCount, string? queryText, IReadOnlyDictionary<string, object?>? parameters, PartitionKey partitionKey)
Parameters
| Type | Name | Description |
|---|---|---|
| string | queryDescription | Description of the query for log messages. |
| double | requestCharge | RU charge for this page. |
| CosmosDiagnostics | diagnostics | Cosmos diagnostics for this page. |
| TimeSpan | duration | Wall-clock duration of this page's round trip. |
| int | resultCount | Number of documents returned on this page. |
| string | queryText | The generated SQL text. |
| IReadOnlyDictionary<string, object> | parameters | Query parameters. |
| PartitionKey | partitionKey | Partition key scope for this query. |
Returns
| Type | Description |
|---|---|
| bool | Whether the page was detected as a cross-partition execution. |
LogPointOperationDiagnostics(string, double, CosmosDiagnostics, TimeSpan)
Logs diagnostics for a point operation (save, delete, point-read) and fires OnQueryDiagnostics(CosmosQueryDiagnostics) with a PointOperation event.
Declaration
protected void LogPointOperationDiagnostics(string operationName, double requestCharge, CosmosDiagnostics diagnostics, TimeSpan duration)
Parameters
| Type | Name | Description |
|---|---|---|
| string | operationName | Name of the operation for log messages. |
| double | requestCharge | RU charge from the response. |
| CosmosDiagnostics | diagnostics | Cosmos diagnostics from the response. |
| TimeSpan | duration | Wall-clock duration of the SDK round trip. |
LogQueryTotalDiagnostics(string, double, TimeSpan, int, string?, IReadOnlyDictionary<string, object?>?, PartitionKey, bool)
Logs the total RU charge for a completed query and fires OnQueryDiagnostics(CosmosQueryDiagnostics) with a QueryTotal event.
Declaration
protected void LogQueryTotalDiagnostics(string queryDescription, double totalRequestCharge, TimeSpan totalDuration, int totalResultCount, string? queryText, IReadOnlyDictionary<string, object?>? parameters, PartitionKey partitionKey, bool isCrossPartition)
Parameters
| Type | Name | Description |
|---|---|---|
| string | queryDescription | Description of the query for log messages. |
| double | totalRequestCharge | Accumulated RU charge across all pages. |
| TimeSpan | totalDuration | Accumulated round-trip duration across all pages. |
| int | totalResultCount | Total document count across all pages. |
| string | queryText | The generated SQL text. |
| IReadOnlyDictionary<string, object> | parameters | Query parameters. |
| PartitionKey | partitionKey | Partition key scope for this query. |
| bool | isCrossPartition | OR across all pages of the cross-partition flag. |
OnQueryDiagnostics(CosmosQueryDiagnostics)
Called for every query execution event: point operations, feed response pages, and query totals. Override in derived classes to route diagnostics to additional sinks for THIS repository only. For app-wide routing use ICosmosQueryLogSink instead. The base ILogger output is not affected by overriding this.
Declaration
protected virtual void OnQueryDiagnostics(CosmosQueryDiagnostics diagnostics)
Parameters
| Type | Name | Description |
|---|---|---|
| CosmosQueryDiagnostics | diagnostics | Structured payload describing the event. |
See Also
| Edit this page View SourceSaveAsync(IList<T>)
Save a list of items to the Cosmos DB container. This method will perform an insert if the item does not exist, otherwise it will perform an update. Items are saved in batches of 50 by default.
Declaration
public virtual Task SaveAsync(IList<T> items)
Parameters
| Type | Name | Description |
|---|---|---|
| IList<T> | items | Items to save |
Returns
| Type | Description |
|---|---|
| Task |
Exceptions
| Type | Condition |
|---|---|
| Exception |
SaveAsync(T)
Save an item to the Cosmos DB container. This method will perform an insert if the item does not exist, otherwise it will perform an update.
Declaration
public virtual Task<T> SaveAsync(T saveThis)
Parameters
| Type | Name | Description |
|---|---|---|
| T | saveThis | The item to save |
Returns
| Type | Description |
|---|---|
| Task<T> |
Exceptions
| Type | Condition |
|---|---|
| InvalidOperationException |
SaveBatchAsync(int, int, T[])
Declaration
protected virtual Task<TransactionalBatchResponse> SaveBatchAsync(int batchCount, int currentBatch, T[] batch)
Parameters
| Type | Name | Description |
|---|---|---|
| int | batchCount | |
| int | currentBatch | |
| T[] | batch |
Returns
| Type | Description |
|---|---|
| Task<TransactionalBatchResponse> |