A high level dotnet API for performing CRUD operations on Dynamo DB entities stored in a single table.
Install from nuget: dotnet add package Turbine
New to Single Table Design? Check out these resources:
A table schema represents the overall structure of a table.
If your partition key and sort key are called pk
and sk
respectively, this a TableSchem
can be defined as:
var tableSchema = new TableSchema(tableName);
Next, an item schema needs to be defined. This is used to determine what properties map to which of the generic pk
and
sk
attributes on the DynamoDB table:
var itemSchema = new Schema(tableName)
.AddEntity<Customer>()
.MapPk(c => c.Country)
.MapSk(c => c.City);
For this item schema, the Partition Key column is mapped to Country
and the Sort Key
column to City
. These properties will be used when querying, updating, deleting or putting items. All other public
properties will be mapped to attribute columns.
Once the table and item schemas have been defined, an instance of Turbine can be created and DynamoDB queried.
For example, to find the first customer in Nottingham:
// Create IAmazonDynamoDB client
var client = ...
using var turbine = new Turbine(client)
var customer = await turbine
.Query(itemSchema)
.WithPk("GB")
.WithSk(SortKey.Exactly("Nottingham"))
.FirstOrDefaultAsync();
If we wanted a list of customers in cities beginning with "L":
// Create DynamoDB client
var client = ...
using var turbine = new Turbine(client)
var customer = await turbine
.Query(itemSchema)
.WithPk("GB")
.WithSk(SortKey.BeginsWith("L"))
.ToListAsync()
ToListAsync
takes an optional limit
parameter, that can be used to set the page size.
To add an item, use UpsertAsync
:
await turbine
.WithSchema(itemSchema)
.UpsertAsync(customer);
UpsertAsync
can also operate on an IEnumerable<T>
, using batches of 25 items.
Additionally, a convenience method, PutIfNotExistsAsync
, will only insert the item if it has a distinct pk
and sk
:
var exists = await turbine
.WithSchema(itemSchema)
.PutIfNotExistsAsync(customer);
Transactions are supported by starting a new transaction scope using turbine.StartTransact()
:
var entityToInsert = new ...
var success =
await transaction
.WithSchema(itemSchema)
.Condition(
entityToInsert,
Condition.AttributeNotExists("pk").And(Condition.AttributeNotExists("sk"))
)
.Upsert(entityToInsert)
.Commit();
StartTransact
returns an object that implements IAsyncDisposable
. On dispose, Commit
will be called if it hasn't already.
Items can be mapped to/ from JSON columns:
var itemSchema = new Schema(tableName)
.AddEntity<Customer>()
.MapPk(c => c.Country)
.MapSk(c => c.City)
.ToJsonAttribute("json");
Global Secondary Indices (GSIs) can be defined on the table:
var tableSchema = TableSchema(tableName)
.AddGsi("gsi1", o =>
o.PkName <- "gsi1pk"
o.SkName <- "gsi1sk");
Then, columns can be mapped in the ItemSchema<T>
, e.g.:
var itemSchema = tableSchema
.AddEntity<Customer>()
.MapPk(c => c.Country)
.MapSk(c => c.City)
.MapGsi("gsi1", gsiPkMapping: c => c.Id, gsiSkMapping: c => c.PostCode);
When querying, the GSI can be specified using QueryGsi
:
turbine
.QueryGsi(itemSchema, "gsi1")
.WithPk("<PK>")
.WithSk(SortKey.Exactly("<SK>"))
.FirstOrDefaultAsync();
v1.0 tasks:
- Update
- Scans
- Filter expressions
- Sort entities
- Nested entities
- Return metrics/ rcus