DynamoDB Entity Definitions in C#
Define type-safe DynamoDB entities in C# using FluentDynamoDB's source generator. This guide shows you how to create entities with partition keys, sort keys, Global Secondary Indexes (GSIs), and Local Secondary Indexes (LSIs) using simple attribute decorations.
- Simple Tables
- Tables with GSI
- Tables with LSI
Simple Table with Partition Key
The most basic entity requires a table name, partition key, and attribute mappings:
using Oproto.FluentDynamoDb.Attributes;
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
[DynamoDbAttribute("email")]
public string Email { get; set; } = string.Empty;
[DynamoDbAttribute("name")]
public string Name { get; set; } = string.Empty;
}
Table with Partition Key and Sort Key
Add a sort key for composite primary keys:
[DynamoDbTable("orders")]
public partial class Order
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string CustomerId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string OrderId { get; set; } = string.Empty;
[DynamoDbAttribute("total")]
public decimal Total { get; set; }
[DynamoDbAttribute("status")]
public string Status { get; set; } = "pending";
}
Using Key Prefixes
Add prefixes for single-table design patterns:
[DynamoDbTable("entities")]
public partial class User
{
[PartitionKey(Prefix = "USER")]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
[SortKey(Prefix = "PROFILE")]
[DynamoDbAttribute("sk")]
public string ProfileType { get; set; } = "MAIN";
}
// Generated key builders:
// UserKeys.Pk("user123") → "USER#user123"
// UserKeys.Sk("MAIN") → "PROFILE#MAIN"
Global Secondary Index (GSI)
GSIs enable alternative query patterns with different partition and sort keys:
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
// GSI partition key - query users by email
[GsiPartitionKey("EmailIndex")]
[DynamoDbAttribute("email")]
public string Email { get; set; } = string.Empty;
[DynamoDbAttribute("name")]
public string Name { get; set; } = string.Empty;
}
GSI with Sort Key
Add a sort key to your GSI for range queries:
[DynamoDbTable("orders")]
public partial class Order
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string OrderId { get; set; } = string.Empty;
// GSI partition key
[GsiPartitionKey("StatusIndex")]
[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
// GSI sort key - enables range queries by date
[GsiSortKey("StatusIndex")]
[DynamoDbAttribute("created_at")]
public DateTime CreatedAt { get; set; }
[DynamoDbAttribute("total")]
public decimal Total { get; set; }
}
// Query pending orders:
// table.Query<Order>()
// .UsingIndex("StatusIndex")
// .Where(x => x.Status == "pending")
Multiple GSIs
Define multiple GSIs for different access patterns:
[DynamoDbTable("products")]
public partial class Product
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string ProductId { get; set; } = string.Empty;
// GSI 1: Query by category
[GsiPartitionKey("CategoryIndex")]
[DynamoDbAttribute("category")]
public string Category { get; set; } = string.Empty;
// GSI 2: Query by vendor
[GsiPartitionKey("VendorIndex")]
[DynamoDbAttribute("vendor_id")]
public string VendorId { get; set; } = string.Empty;
[GsiSortKey("VendorIndex")]
[DynamoDbAttribute("price")]
public decimal Price { get; set; }
}
// Generated index constants:
// ProductIndexes.CategoryIndex → "CategoryIndex"
// ProductIndexes.VendorIndex → "VendorIndex"
Local Secondary Index (LSI)
LSIs share the same partition key as the base table but have a different sort key:
[DynamoDbTable("orders")]
public partial class Order
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string CustomerId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string OrderId { get; set; } = string.Empty;
// LSI - query orders by date within a customer
[LsiSortKey("orders-by-date")]
[DynamoDbAttribute("order_date")]
public string OrderDate { get; set; } = string.Empty;
// Another LSI - query orders by status
[LsiSortKey("orders-by-status")]
[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
[DynamoDbAttribute("total")]
public decimal Total { get; set; }
}
LSI vs GSI Comparison
| Feature | Local Secondary Index | Global Secondary Index |
|---|---|---|
| Partition Key | Same as base table | Can be different |
| Sort Key | Different from base table | Can be different |
| Created | At table creation only | Anytime |
| Consistency | Strong or eventual | Eventual only |
| Throughput | Shares with base table | Independent |
| Maximum | 5 per table | 20 per table |
When to Use LSI
Use LSIs when you need:
- Alternative sort orders on the same partition key
- Strong consistency reads on the index
- To avoid additional throughput costs
// Example: Customer orders with multiple sort options
[DynamoDbTable("customer-orders")]
public partial class CustomerOrder
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string CustomerId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string OrderId { get; set; } = string.Empty;
// LSI: Sort by order date
[LsiSortKey("by-date")]
[DynamoDbAttribute("order_date")]
public DateTime OrderDate { get; set; }
// LSI: Sort by total amount
[LsiSortKey("by-total")]
[DynamoDbAttribute("total")]
public decimal Total { get; set; }
}
Learn More
- Entity Definition Guide - Complete entity definition patterns
- Source Generator Configuration - All available attributes
- Global Secondary Indexes - Advanced GSI query patterns
- Single-Table Design - Multi-entity table patterns