Kanject.Core.NoSqlDatabase.Abstractions
4.5.1
Prefix Reserved
See the version list below for details.
dotnet add package Kanject.Core.NoSqlDatabase.Abstractions --version 4.5.1
NuGet\Install-Package Kanject.Core.NoSqlDatabase.Abstractions -Version 4.5.1
<PackageReference Include="Kanject.Core.NoSqlDatabase.Abstractions" Version="4.5.1" />
<PackageVersion Include="Kanject.Core.NoSqlDatabase.Abstractions" Version="4.5.1" />
<PackageReference Include="Kanject.Core.NoSqlDatabase.Abstractions" />
paket add Kanject.Core.NoSqlDatabase.Abstractions --version 4.5.1
#r "nuget: Kanject.Core.NoSqlDatabase.Abstractions, 4.5.1"
#:package Kanject.Core.NoSqlDatabase.Abstractions@4.5.1
#addin nuget:?package=Kanject.Core.NoSqlDatabase.Abstractions&version=4.5.1
#tool nuget:?package=Kanject.Core.NoSqlDatabase.Abstractions&version=4.5.1
Kanject.Core.NoSqlDatabase.Abstractions
Provider-neutral contracts for the Kanject NoSQL data layer. The package defines the IEntity, IRepository<TEntity>, IUnitOfWork and IDbContext interfaces, a portable QueryOperator vocabulary with typed exceptions, shared request-filter models, and a few geospatial helpers. Domain and application code references this package. A provider package supplies the implementation for a specific store: Amazon DynamoDB, Amazon Keyspaces or ScyllaDB.
This package does not include a storage engine, and there is no in-memory NoSQL provider. To read or write data, you also need one of the Kanject provider packages listed under Related packages. Those packages require a commercial license.
Installation
dotnet add package Kanject.Core.NoSqlDatabase.Abstractions
Targets .NET 8, .NET 9 and .NET 10. Depends on Kanject.Core.
Quick start
Describe the entity against the shared contract. IEntity requires a string PartitionKey. IBaseEntityIdentifier<T> adds a typed Id.
using Kanject.Core.NoSqlDatabase.Abstractions.Interfaces;
public sealed class Order : IBaseEntityIdentifier<string>
{
public string PartitionKey { get; set; } = string.Empty;
public string Id { get; set; } = string.Empty;
public string CustomerId { get; set; } = string.Empty;
public decimal Total { get; set; }
}
Write application services against IRepository<TEntity> and IUnitOfWork:
using Kanject.Core.NoSqlDatabase.Abstractions.Interfaces;
public sealed class OrderService(IRepository<Order> orders, IUnitOfWork unitOfWork)
{
public Task<Order?> FindAsync(string partitionKey) =>
orders.GetSingleAsync(partitionKey);
public Task SaveAsync(Order order) =>
orders.AddOrUpdateAsync(order);
public Task ImportAsync(IList<Order> batch, CancellationToken cancellationToken) =>
orders.InsertRangeAsync(batch, cancellationToken);
// Commit two writes as one unit.
public async Task ReplaceAsync(Order current, Order replacement, CancellationToken cancellationToken)
{
orders.SetUnitOfWork(unitOfWork); // enlist this repository's writes
unitOfWork.BeginTransaction();
await orders.RemoveAsync(current);
await orders.InsertAsync(replacement);
await unitOfWork.CommitAsync(cancellationToken);
}
}
In the host, register a provider and make IRepository<Order> resolve to that provider's repository for Order. Kanject providers register IUnitOfWork for you. If your provider gives you a concrete repository class for each entity, forward the shared interface to it:
// OrderRepository is the repository class your provider gives you for Order,
// for example a source-generated [Repository] partial class.
builder.Services.AddScoped<IRepository<Order>>(sp => sp.GetRequiredService<OrderRepository>());
builder.Services.AddScoped<OrderService>();
With a real provider, the entity also implements that provider's entity interface and carries its mapping attributes. DynamoDB entities implement IDynamoDbEntity, which adds SortKey. Entities for the CQL providers implement ICqlEntity. Each provider's repository interface derives from IRepository<TEntity>, so services written against this package keep working.
Repositories
| Member | Purpose |
|---|---|
GetSingleAsync(string id) |
Fetches one item, or returns null. The shipped providers treat id as the partition-key value. |
InsertAsync / InsertRangeAsync |
Writes new items. |
AddOrUpdateAsync / AddOrUpdateRangeAsync |
Inserts an item or replaces the existing one. |
UpdateAsync / UpdateRangeAsync |
Updates existing items. |
RemoveAsync / RemoveRangeAsync |
Deletes items. |
SqlQueryAsync(string) / SqlQueryAsync<TData>(string) / ExecuteQueryAsync(string) |
Runs a raw statement in the store's own dialect: PartiQL on DynamoDB, CQL on Keyspaces and ScyllaDB. |
SetUnitOfWork(...) / SetDbContext(...) |
Binds the repository to a unit of work or a database context. |
DbContext / CurrentTableName |
Returns the bound context and the table the repository writes to. |
Every *RangeAsync method has an overload that takes a CancellationToken. The default interface implementation forwards to the overload without a token, so whether the token is honoured depends on the provider.
On the CQL providers, SqlQueryAsync<TData> supports only the repository's own entity type. Use SqlQueryAsync(string) there instead.
Units of work and transactions
IUnitOfWork groups writes. Call BeginTransaction(), write through repositories bound with SetUnitOfWork(...), then call CommitAsync(...). IsTransactionEnabled is true while a transaction is open.
- How a commit is carried out depends on the provider. The DynamoDB providers submit the buffered writes as one transactional write.
CommitAsync(string clientRequestToken, ...)forwards the token as that request's idempotency token. The CQL providers (Keyspaces, ScyllaDB) flush the writes as aLOGGED BATCH. Their token overload requires a non-empty token but does not use it. - The unit of work is scoped. Kanject providers register
IUnitOfWorkwith a scoped lifetime because it buffers per-unit transaction state. Do not capture it in a singleton. - Resolve repositories through DI or through the provider's own unit-of-work interface. The shipped providers do not implement the shared
IUnitOfWork.Repository<TEntity>()overload, and calling it throws. - Read before you begin. Some providers reject reads such as
GetSingleAsyncwhile a transaction is open.
Query operators
Provider query builders accept QueryOperator values. The enum's integer values are fixed, so the wire format stays the same when a member is renamed.
| Operators | Portability |
|---|---|
Equal, NotEqual, LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual, In, NotIn, Between, Contains, NotContains |
Comparison and filter operators. NotEqual, NotIn and NotContains work in filter expressions only. |
BeginsWith, NotBeginsWith |
DynamoDB-native. CQL has no equivalent on arbitrary columns. |
AttributeExist, AttributeNotExist |
DynamoDB only. |
ContainsKey |
CQL only (map-key membership). |
Set, Increment, Decrement, Remove, Add, Delete |
Update and delete actions. |
SetIncrement, SetDecrement and RemoveAttribute are obsolete aliases for Increment, Decrement and Remove.
When a provider can't translate an operator, it throws QueryOperatorNotSupportedException. The exception's Operator and Provider properties are populated, so code that targets several stores can catch one typed exception:
using Kanject.Core.NoSqlDatabase.Abstractions.Exceptions;
try
{
await RunPrefixSearchAsync(prefix);
}
catch (QueryOperatorNotSupportedException ex)
{
logger.LogWarning("{Provider} cannot evaluate {Operator}; falling back.", ex.Provider, ex.Operator);
await RunFullScanAsync(prefix);
}
Two more exceptions cover model problems:
InvalidModelBuilderException: an entity or model declaration fails a provider's pre-flight validation. It exposesEntityTypeName,TableName,MemberNameandProvider.CreateTableModelBuilderException: a provider fails to create a table from a valid model. It exposesTableName,KeyspaceandProvider.
Request filters
These are small shapes for API query models. The interfaces all derive from the IFilter marker.
| Type | Members |
|---|---|
IPaginationFilter / DataPaginationFilter |
PageToken, PageSize (JSON: pageToken, pageSize) |
IDateRangeFilter / DateRangeFilter |
StartDate, EndDate (JSON: startDate, endDate) |
IQueryFilter |
Query |
IStatusFilter |
Status |
IAuditFilter |
CreatedBy, ModifiedBy |
"...".ParseFilter<TFilter>() in Kanject.Core.NoSqlDatabase.Abstractions.Filters.Extensions deserializes a JSON filter string with System.Text.Json. It returns null when the JSON is invalid. It uses reflection-based serialization.
Geospatial helpers
GeoPoint is an immutable WGS-84 latitude/longitude value. Its constructor throws ArgumentOutOfRangeException outside ±90° and ±180°. SphericalMath performs allocation-free great-circle calculations on a sphere with a mean radius of 6,371 km.
using Kanject.Core.NoSqlDatabase.Abstractions.Helpers.GeoHashing;
using Kanject.Core.NoSqlDatabase.Abstractions.Helpers.GeoHashing.Models;
var losAngeles = new GeoPoint(34.0522, -118.2437);
var lasVegas = new GeoPoint(36.1699, -115.1398);
double metres = SphericalMath.Distance(losAngeles, lasVegas); // haversine, about 367,600 m
double bearing = SphericalMath.Bearing(losAngeles, lasVegas); // about 49.3 degrees from true north
GeoPoint waypoint = SphericalMath.Destination(losAngeles, bearing, 100_000);
SphericalMath.ToRadians and ToDegrees are also public. GeoPoint.Lat and GeoPoint.Lon are obsolete aliases for Latitude and Longitude.
GeohashCodec encodes and decodes standard geohashes (the same strings other geohash libraries produce), and GeohashQueries builds on it for lookups:
using Kanject.Core.NoSqlDatabase.Abstractions.Helpers.GeoHashing.Enums; // Direction
string hash = GeohashCodec.Encode(new GeoPoint(42.6, -5.6), precision: 5); // "ezs42"
var (centre, latitudeError, longitudeError) = GeohashCodec.Decode(hash); // cell centre ± half its size
string north = GeohashCodec.GetAdjacent(hash, Direction.North); // "ezs48"
IReadOnlySet<string> around = GeohashQueries.GetNeighbors(hash); // the 8 surrounding cells
ISet<string> covering = GeohashQueries.GetHashesInRadius(losAngeles, 1_000, precision: 7);
GetHashesInRadius returns every cell the circle touches, so a record stored under any point inside the circle is in one of them. Edge cells can reach past the circle, so filter the results by exact distance. A covering that would need more than 100,000 cells throws ArgumentOutOfRangeException instead of returning a partial set; use a lower precision for large radii. At the poles GetAdjacent keeps the row (there is no cell beyond the pole) and GetNeighbors leaves those cells out.
Geohashes computed by earlier versions of this package are not standard geohashes and do not round-trip. Recompute any stored geohash with the current
Encodebefore querying against it.
Trimming and Native AOT
The package is built with the .NET trim and AOT analyzers enabled (IsAotCompatible). The data-access members of IRepository, IRepository<TEntity> and IUnitOfWork are annotated [RequiresUnreferencedCode] and [RequiresDynamicCode], and so is ParseFilter. The annotations exist because the providers' general query paths bind values dynamically or map entities by reflection. In a trimmed or Native AOT publish, calls to these members report IL2026 and IL3050 warnings at your call sites. Entities, filter models, enums, exceptions and the geospatial helpers carry no such annotations.
Public surface at a glance
| Type | Purpose |
|---|---|
IEntity, IBaseEntityIdentifier<T> |
Entity contracts (PartitionKey, Id) |
IRepository, IRepository<TEntity> |
Untyped and typed repository contracts |
IUnitOfWork |
Groups writes into a transaction, then commits them |
IDbContext |
Disposable marker for a provider's database context |
QueryOperator, QueryCondition, OrderBy, QueryExecutionStatus |
Query vocabulary shared by providers |
QueryOperatorNotSupportedException, InvalidModelBuilderException, CreateTableModelBuilderException |
Typed provider failures |
IFilter and its derivatives, DataPaginationFilter, DateRangeFilter |
Request-filter shapes |
GeoPoint, SphericalMath |
Coordinates and great-circle geometry |
GeohashCodec, GeohashQueries, Direction |
Standard geohash encoding, neighbours and radius coverings |
Related packages
| Package | Role | Availability |
|---|---|---|
Kanject.Core |
Core runtime this package depends on | nuget.org |
Kanject.Core.SqlDatabase.Abstractions |
Contracts for the relational data layer | nuget.org |
Kanject.Core.NoSqlDatabase.Provider.DynamoDbV2 |
Amazon DynamoDB provider | Commercial license (not on nuget.org) |
Kanject.Core.NoSqlDatabase.Provider.DynamoDb |
Amazon DynamoDB provider (original runtime) | Commercial license (not on nuget.org) |
Kanject.Core.NoSqlDatabase.Provider.DynamoDb.Abstractions |
DynamoDB-specific contracts (IDynamoDbEntity, query configuration) |
Commercial license (not on nuget.org) |
Kanject.Core.NoSqlDatabase.Provider.DynamoDb.Annotations |
DynamoDB source generators and analyzers | Commercial license (not on nuget.org) |
Kanject.Core.NoSqlDatabase.Provider.Keyspaces |
Amazon Keyspaces (Cassandra-compatible) provider | Commercial license (not on nuget.org) |
Kanject.Core.NoSqlDatabase.Provider.Keyspaces.Annotations |
Keyspaces source generators and analyzers | Commercial license (not on nuget.org) |
Kanject.Core.NoSqlDatabase.Provider.Scylla |
ScyllaDB provider | Commercial license (not on nuget.org) |
Kanject.Core.NoSqlDatabase.Provider.Cql |
Shared CQL runtime used by the Keyspaces and ScyllaDB providers | Commercial license (not on nuget.org) |
Kanject.Core.NoSqlDatabase.Provider.Cql.Abstractions |
CQL-specific contracts (ICqlEntity) |
Commercial license (not on nuget.org) |
License
Licensed under the Kanject Code Libraries License Agreement (KCLLA); the full text ships in this package as LICENSE.md. Organizations whose trailing-twelve-month gross revenue and total funding raised are each below US$250,000 may use it at no cost under the Free Tier. At or above either threshold a commercial license is required — contact commercial@kanjectbusiness.com.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Kanject.Core (>= 3.13.1)
-
net8.0
- Kanject.Core (>= 3.13.1)
-
net9.0
- Kanject.Core (>= 3.13.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.7.0 | 84 | 10/5/2026 |
| 4.6.0 | 90 | 10/2/2026 |
| 4.5.1 | 115 | 9/27/2026 |
| 4.5.0 | 98 | 9/27/2026 |
| 4.4.7 | 93 | 9/26/2026 |
| 4.4.6 | 121 | 9/7/2026 |
| 4.4.5 | 104 | 8/27/2026 |
| 4.4.4 | 107 | 8/22/2026 |
| 4.4.3 | 125 | 8/10/2026 |
| 4.4.2 | 107 | 8/9/2026 |
| 4.4.1 | 130 | 8/5/2026 |
| 4.4.0 | 124 | 8/5/2026 |
| 4.3.0 | 121 | 8/3/2026 |
| 4.2.8 | 124 | 7/30/2026 |
| 4.2.7 | 131 | 7/18/2026 |
| 4.2.6 | 135 | 7/13/2026 |
| 4.2.5 | 128 | 7/11/2026 |
| 4.2.4 | 132 | 7/11/2026 |
| 4.2.3 | 128 | 7/9/2026 |
| 4.2.2 | 129 | 7/9/2026 |