# Kanject.Core.NoSqlDatabase

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

> **Need relational?:** See `Kanject.Core.SqlDatabase` — the relational sibling with an Aurora DSQL provider (v1.0.0), same `[Repository]` ergonomics. /docs/core-sql

> **At Bean & Bark:** Bean & Bark's orders fill a DynamoDB table exactly like the one a `[Repository]` maps here. Build the data layer step by step in [The orders table, typed](https://www.kanject.com/docs/core-bean-bark-orders/); the [CLI series](https://www.kanject.com/docs/cli-bean-bark/) ships the API that writes it and the [DynoStudio series](https://www.kanject.com/docs/dynostudio-bean-bark/) reads it — same table, both sides of the stack.

---
_Source: https://www.kanject.com/docs/core-nosql/ · Kanject Docs_
