Amazon DynamoDB is a fast and scalable NoSQL database that requires a different approach than traditional relational design. One of the most recommended patterns for working efficiently with DynamoDB is Single-Table Design (STD), which stores multiple entities in a single table to optimize queries and reduce read costs.
In this article, I’ll briefly explain what Single-Table Design is, how to implement it easily with DynamoSharp (a C# ORM), and show a practical example of a one-to-many relationship.
What is Single-Table Design?
Unlike relational databases that normalize data across multiple tables, Single-Table Design in DynamoDB stores all entities in one table. It intelligently uses partition keys, sort keys (PartitionKey
and SortKey
), and global secondary indexes (GSI
).
This approach models relationships between entities (like one-to-many or many-to-many) using well-defined access patterns, resulting in fast and efficient operations.
What is DynamoSharp?
DynamoSharp is an ORM (Object-Relational Mapping) for C# that simplifies using DynamoDB with Single-Table Design. Key features include:
- Custom mapping for primary/secondary keys.
- Support for entity relationships (one-to-many, many-to-many).
- Automatic key generation (convention-based or configurable).
- Optimistic locking for version control.
- Retry strategies for DynamoDB errors.
- Automatic change tracking for entities.
DynamoSharp lets you focus on business logic without worrying about DynamoDB’s underlying complexity.
Example: One-to-Many Relationship
Let’s explore an example where an Order
has many Items
(a one-to-many relationship).
Installation
dotnet add package DynamoSharp
Configuration
builder.Services.AddDynamoSharp(RegionEndpoint.USEast1);
builder.Services.AddDynamoSharpContext<OrganizationContext>(
new TableSchema.Builder()
.WithTableName("dynamosharp")
.Build()
);
Entities
public class Item
{
public int Id { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
public int Units { get; set; }
...
}
public class Order
{
public int Id { get; set; }
public int BuyerId { get; set; }
public Address Address { get; set; }
public Status Status { get; set; }
public DateTime Date { get; set; }
private readonly List<Item> _items = new List<Item>();
public IReadOnlyCollection<Item> Items => _items;
...
}
Context Setup
public class EcommerceContext : DynamoSharpContext
{
public IDynamoDbSet<Order> Orders { get; private set; } = null!;
public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) : base (dynamoDbContextAdapter, tableSchema)
{
}
public override void OnModelCreating(IModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasOneToMany(o => o.Items);
}
}
Save
var order = new Order.Builder()
.WithId(1)
.WithBuyerId(5)
.WithAddress("Street 1", "City 1", "State 1", "ZipCode 1")
.WithStatus(Status.Pending)
.WithDate(DateTime.Now)
.Build();
order.AddProduct(2, "Product 1", 26, 93);
ecommerceContext.Orders.Add(order);
ecommerceContext.TransactWriter.SaveChangesAsync(cancellationToken);
Result in DynamoDB
PartitionKey | SortKey | Id | BuyerId | ProductName | UnitPrice | Units |
---|---|---|---|---|---|---|
ORDER#1 | ORDER#1 | 1 | 5 | |||
ORDER#1 | ITEM#2 | 2 | Product 1 | 26.0 | 93 |
As shown, both the order and its items share the same partition key (ORDER#1
). This allows querying the entire order and its items in a single read operation.
Querying an Order
var order = ecommerceContext.Query<Order>()
.PartitionKey("ORDER#1")
.ToEntityAsync(cancellationToken);
Example on GitHub
Conclusion
Single-Table Design may seem intimidating at first, but tools like DynamoSharp make it accessible for C# developers. This library abstracts DynamoDB’s complexity so you can focus on your models and relationships while maintaining optimal performance and clean code.
Want to learn more? Visit the official GitHub repository:
👉 https://github.com/ChrixApp/DynamoSharp
Top comments (0)