Kanject.Core.NoSqlDatabase
Type-safe NoSQL access across DynamoDB, ScyllaDB, and Amazon Keyspaces. Declare a [Repository] interface, annotate the entity with [PartitionKey] / [SortKey] / [Indexed], and a source generator emits the implementation — including secondary-index queries — at compile time.
Providers
The same [Repository] interfaces and entity annotations compile against three engines. Swap the registration extension, leave the rest of your code untouched.
Install
dotnet add package Kanject.Core.NoSqlDatabase Register
using Kanject.Core.NoSqlDatabase.Provider.DynamoDbV2.Extensions;
builder.Services.AddDynamoNoSqlDatabase(options =>
{
options.Namespace = appSettings.DatabaseNamespace; // e.g. "prod"
options.AwsRegion = appSettings.AwsRegion;
}); Switch providers with one line — AddScyllaNoSqlDatabase(...) or AddKeyspacesNoSqlDatabase(...) in place of AddDynamoNoSqlDatabase(...). Namespace prefixes every table/keyspace at runtime (prod-Products, staging-Products) so multiple stages can share an account safely.
Define a repository
using Kanject.Core.NoSqlDatabase.Abstractions;
using Kanject.Core.NoSqlDatabase.Abstractions.Attributes;
[Repository(table: "Products")]
public partial interface IProductRepository : IRepository<Product>
{
[Query(by: nameof(Product.SellerId))]
Task<IReadOnlyList<Product>> ListBySellerAsync(string sellerId);
[Query(by: nameof(Product.Status), index: "ByStatus")]
Task<IReadOnlyList<Product>> ListByStatusAsync(ProductStatus status);
}
public record Product
{
[PartitionKey] public Guid Id { get; init; }
[SortKey, Indexed("BySeller")] public string SellerId { get; init; } = default!;
[Indexed("ByStatus")] public ProductStatus Status { get; init; }
public string Title { get; init; } = default!;
public decimal Price { get; init; }
} The partial interface is filled in by the source generator. [Query] methods compile to GSI lookups; [Indexed] declares the GSIs they read from.
Use it
public class ProductService(IProductRepository products)
{
public Task<Product?> GetAsync(Guid id) => products.FindAsync(id);
public Task<IReadOnlyList<Product>> ListByStatusAsync(ProductStatus s)
=> products.ListByStatusAsync(s);
public Task UpdatePriceAsync(Guid id, decimal price)
=> products.UpdateAsync(id, p => p with { Price = price });
} What ships with it
- Three providers: DynamoDB, ScyllaDB, Amazon Keyspaces — same
[Repository]shape - Source-generated repository implementations — zero reflection at runtime
- Strongly-typed GSI / LSI queries with compile-time index validation
- Automatic
Namespaceprefixing for multi-stage isolation - Optimistic concurrency via
[Version]attribute - Mockable
IRepository<T>interface for unit tests
Kanject.Core.SqlDatabase (beta) — the relational sibling with an Aurora DSQL provider, same [Repository] ergonomics. /docs/core-sql