Kanject marker indexes

View .md

A Kanject marker index is not a GSI. It is a model-declared access path materialized as reserved rows in the base table. Markers can enforce uniqueness, group related items, order values for range reads, or support search. Kanject generates both the index-aware write methods that maintain those rows and the finder methods that read through them.

The model is the definition

csharp
[Searchable(template: "Marketplace#{ItemCountryId}")]public string ItemName { get; set; }[Range(name: "Price", template: "Marketplace#{ItemCountryId}")]public decimal ItemPrice { get; set; }[Collection(name: "SellerMarketplaceItems")]public Guid SellerUserId { get; set; }[Unique("UniqueBundleRef")]public string BundleReference { get; set; }

The annotation carries more than a property name. A marker key can depend on its name, a template or rangeTemplate, and several properties from the entity. Composite unique markers also share a named group. That model metadata — together with the matching generator contract — is the canonical definition.

  • [Unique] / [CompositeUnique] reserve a key for a value or named value group so competing owners cannot claim the same marker.
  • [Collection] materializes a membership access path, optionally composing other properties into its ordering key.
  • [Range] materializes a value-ordered access path for generated range finders.
  • [Searchable] materializes the tokens and references used by generated search methods.

Writes maintain the base item and markers together

csharp
await Repository.InsertWithIndexAsync(entity);await Repository.CommitAsync();await Repository.UpdateWithIndexAsync(updatedEntity, previousEntity);await Repository.CommitAsync();await Repository.RemoveWithIndexAsync(entity);await Repository.CommitAsync();

The generated *WithIndexAsync methods stage the base-item operation and its marker operations in the same transaction. CommitAsync() is the point that submits that unit of work. Updates receive the previous entity because changing a participating property may require deleting old marker keys before inserting the new ones.

Generated finders turn markers into access paths

csharp
var (bundles, page) =    await Repository.FindBuyerBundlesAsync(buyerUserId, pagination);var bundle =    await Repository.FindBundleByUniqueBundleRefAsync(reference);

A generated finder usually reads marker rows first and then resolves their base-item references. This is why markers are not only constraints: collection, range, and search markers are application query paths. DynoStudio can expose the definition, composed key shape, reserved rows, owner references, generated finder, and the extra read phase involved.

A generated Is…UniqueAsync method is useful for friendly validation, but it is not the concurrency boundary by itself. Correctness comes from the transactional index-aware write; another writer can race a standalone precheck.

Existing data needs a migration

Adding or changing an annotation governs index-aware writes made after that code ships; it does not populate marker rows for items already stored. Existing-data backfill, reconciliation, or removal must run as a separate durable migration that consumes the same Kanject model metadata and marker composer as the application.

  • Pin the Kanject generator or marker-contract version and record a definition hash.
  • Checkpoint progress so a long run can resume safely, and make retries idempotent.
  • Compose every marker type from complete entities, including templates, search tokens, reference fields, and composite groups.
  • Detect unique collisions without choosing a winner, and report enough owner information to resolve them.
  • Account for concurrent application writes, throttling, cancellation, audit history, and a controlled rollback or cleanup path.

Related: Tables, indexes & replicas for supported control-plane DDL · CREATE GLOBAL INDEX for native DynamoDB secondary indexes · Kanject.Core.NoSqlDatabase for the annotations and generated repositories.

Was this page helpful?