Kanject.Core.NoSqlDatabase

View .md

Type-safe NoSQL access across DynamoDB, ScyllaDB, and Amazon Keyspaces. Declare a [DbContext], give entities their key layout with AWS key attributes plus Kanject's [KeyTemplate], then mark an empty partial [Repository] class. The source generator emits the implementation — typed key composers, CRUD, finders, GSI queries, and child repositories — at compile time, with zero reflection at runtime.

Providers

The same context, entity, and repository model compiles against three engines. Swap the registration call; leave the rest of your application code untouched.

DynamoDB
`AddDynamoDbContext<TContext>()` + `RegisterDynamoDbRepository()` — single-digit-ms reads/writes, on-demand capacity, multi-region tables. The default provider. /docs/core-nosql
ScyllaDB
`AddScyllaDbContext<TContext>()` — same context and `[Repository]` shape on self-hosted or ScyllaCloud clusters, when you need cost ceilings or co-location.
Keyspaces
`AddDbContext<TContext>()` (Keyspaces.Extensions) — Apache Cassandra on AWS, CQL underneath, same context and `[Repository]` model. Pick this for Cassandra-native customers.

Install

bash
dotnet add package Kanject.Core.NoSqlDatabase.Provider.DynamoDb

Each provider ships as its own package — Kanject.Core.NoSqlDatabase.Provider.DynamoDb, …Provider.Scylla, or …Provider.Keyspaces — and pulls in the shared abstractions transitively.

Define the entity, context, and repository

csharp
using Amazon.DynamoDBv2.DataModel;using Kanject.Core.NoSqlDatabase.Provider.DynamoDb.Abstractions.DataContext.EntityBuilder;using Kanject.Core.NoSqlDatabase.Provider.DynamoDb.Abstractions.Interfaces;using Kanject.Core.NoSqlDatabase.Provider.DynamoDb.Annotations.Attributes;namespace Demo.Catalog.Data;// The entity. The [DynamoDB*] attributes are AWS's (Amazon.DynamoDBv2.// DataModel) — they name the physical key attributes. [KeyTemplate] is// Kanject's — it composes each key's value from the entity's own properties.public sealed class Product : IDynamoDbEntity{    [DynamoDBHashKey("pk")]    [KeyTemplate("PRODUCT#{Sku}")]    public string PartitionKey { get; set; } = string.Empty;    [DynamoDBRangeKey("sk")]    [KeyTemplate("META")]          // literal-only template → sk = "META"    public string SortKey { get; set; } = string.Empty;    [DynamoDBProperty] public string Sku        { get; set; } = string.Empty;    [DynamoDBProperty] public string Name       { get; set; } = string.Empty;    [DynamoDBProperty] public long   PriceCents { get; set; }}// One table, one context. MapEntity names the physical table and registers// every entity that lives in it (add more entities to the same call).[DbContext]public partial class CatalogDbContext{    protected override void OnModelCreating(EntityModelBuilder builder)        => builder.MapEntity("Catalog", new Product());}// Declare the repository as an empty partial. The source generator emits the// CRUD, the typed finders, and an IProductRepository interface it registers// for you (GenerateInterface defaults true).[Repository(Name = "Products", Version = 1, Entity = typeof(Product))]public partial class ProductRepository;

The [KeyTemplate] values become strongly typed key composers: set Sku = "sku-42" and the stored pk is PRODUCT#sku-42. [DbContext]'s MapEntity names the physical table and registers each entity in it. The [Repository] partial is completed by the generator — you write no method bodies.

Register

csharp
using Kanject.Core.NoSqlDatabase.Provider.DynamoDb.Extensions;builder.Services.AddDynamoDbContext<CatalogDbContext>(    options: cfg =>    {        cfg.Namespace = appSettings.Stage;    // "prod" → physical table "prod.Catalog"        cfg.AwsRegion = appSettings.AwsRegion;        cfg.CreateTableCheck = true;          // create the table on first run if missing    },    dbContextOptions: options => options.ServiceCollection.RegisterDynamoDbRepository());

Switch providers by swapping the call — AddScyllaDbContext<TContext>(...) or Keyspaces' AddDbContext<TContext>(...) — leaving the entity and repository untouched. cfg.Namespace prefixes the physical table so multiple stages share an account safely: namespace prod maps table Catalog to prod.Catalog (dot-joined, the namespace lower-cased). dev and prod therefore never touch each other's data.

Use it

csharp
public sealed class ProductService(IProductRepository products){    // Write: set the template fields; the pk/sk strings compose themselves.    public Task AddAsync(Product product) => products.InsertAsync(product);    // Read one item. The finder takes every key-template parameter — here just    // the Sku (the sort key is the literal "META"). Keys resolve to    // pk = PRODUCT#<sku>, sk = META.    public Task<Product?> GetAsync(string sku)        => products.FindProductAsync(sku);    public async Task UpdatePriceAsync(string sku, long priceCents)    {        var product = await products.FindProductAsync(sku);        if (product is null) return;        product.PriceCents = priceCents;        await products.AddOrUpdateAsync(product);    }}

Inject the generated IProductRepository. The single-item finder is Find{Entity}Async (it takes every key-template parameter); a whole partition uses the pluralized, paginated Find{Entities}Async. Base CRUD — InsertAsync, AddOrUpdateAsync, UpdateAsync, RemoveAsync, GetSingleAsync — comes from IRepository<TEntity>.

Global secondary indexes

Add a [DynamoDbGsi(Name = "Gsi1Index", HashKey = "...", RangeKey = "...")] to the entity to declare an index, and a [DynamoDbGsiAlias<TEntity>(name: "Gsi1Index", alias: "SellerProduct")] to give an access pattern a readable name. The generator emits a static finder for it named from the alias and index — FindSellerProductsUsingGsi1IndexAsync(...) — so an index query reads as clearly as a primary-key one. [DynamoDbLsi] declares local secondary indexes the same way.

What ships with it

  • Three providers: DynamoDB, ScyllaDB, Amazon Keyspaces — same [DbContext] and [Repository] shape
  • Source-generated partial repositories — CRUD, typed finders, and an auto-registered I<Name>Repository, zero reflection at runtime
  • [KeyTemplate] key composition for single-table designs — PRODUCT#{Sku} composes pk from the entity
  • Strongly-typed GSI / LSI access patterns via [DynamoDbGsi] / [DynamoDbGsiAlias<T>] / [DynamoDbLsi]
  • cfg.Namespace prefixes the physical table (prod.Catalog) for multi-stage isolation
  • [EntityRepository<T>] / [EmbedRepository<T>] for child collections and composed repositories under one unit of work
  • Compile-time diagnostics (KAN… / KANJECT…) for invalid key templates, indexes, and repository shapes
Was this page helpful?