Entities
Everything in Oproto.FluentDynamoDb is entity-based. An entity is a C# class that maps directly to items in your DynamoDB table. By decorating your classes with attributes, the source generator creates type-safe table classes, key builders, and field constants that make working with DynamoDB feel natural in C#.
Entities serve as the foundation for:
- Type-safe operations - Get, Put, Update, and Delete with compile-time checking
- Key generation - Automatic key formatting with prefixes and computed values
- Query building - Lambda expressions and format strings that reference your properties
Entity Declaration
Every DynamoDB entity requires:
[DynamoDbTable]attribute - Specifies the table namepartialkeyword - Enables source generation to extend your class- At least one
[PartitionKey]- Defines the partition key [DynamoDbAttribute]on properties - Maps properties to DynamoDB attributes
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;
}
The partial keyword is essential—it allows the source generator to create companion code including:
- A table class (
UsersTable) with CRUD operations - A fields class (
UserFields) with attribute name constants - A keys class (
UserKeys) with key formatting methods
Partition Keys and Sort Keys
Partition Key
Every entity must have exactly one partition key, which uniquely identifies items (or groups of items when combined with a sort key):
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
}
Sort Key
Add a sort key for composite primary keys, enabling multiple items to share the same partition 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;
}
Key Prefixes
Add prefixes to partition and sort keys for better organization, especially in single-table designs:
[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";
}
The source generator creates key builder methods that automatically apply prefixes:
// Generated UserKeys class
public static class UserKeys
{
public static string Pk(string userId)
{
return $"USER#{userId}";
}
public static string Sk(string profileType)
{
return $"PROFILE#{profileType}";
}
}
// Usage
UserKeys.Pk("user123") // Returns "USER#user123"
UserKeys.Sk("MAIN") // Returns "PROFILE#MAIN"
Custom Separators
Change the default # separator to another character:
[PartitionKey(Prefix = "USER", Separator = "|")]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
// UserKeys.Pk("user123") returns "USER|user123"
Property Mapping
The [DynamoDbAttribute] attribute maps C# property names to DynamoDB attribute names:
[DynamoDbTable("products")]
public partial class Product
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string ProductId { get; set; } = string.Empty;
// Property name: Name → DynamoDB attribute: product_name
[DynamoDbAttribute("product_name")]
public string Name { get; set; } = string.Empty;
// Property name: Price → DynamoDB attribute: price
[DynamoDbAttribute("price")]
public decimal Price { get; set; }
// Property name: CreatedAt → DynamoDB attribute: created_at
[DynamoDbAttribute("created_at")]
public DateTime CreatedAt { get; set; }
}
The source generator creates a Fields class with constants for each attribute:
// Generated ProductFields class
public static class ProductFields
{
public const string ProductId = "pk";
public const string Name = "product_name";
public const string Price = "price";
public const string CreatedAt = "created_at";
}
Naming Conventions
Choose attribute names based on your table design:
- Single-table design: Use short, generic names (
pk,sk,gsi1pk) since multiple entity types share the same attributes - Dedicated tables: Use descriptive names (
userId,email,orderDate) for clarity
// Single-table design - generic attribute names
[DynamoDbTable("entities")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")] // Generic
public string UserId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")] // Generic
public string SortKey { get; set; } = string.Empty;
}
// Dedicated table - descriptive attribute names
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("userId")] // Descriptive
public string UserId { get; set; } = string.Empty;
[DynamoDbAttribute("email")] // Descriptive
public string Email { get; set; } = string.Empty;
}
Computed Keys
Use the [Computed] attribute to create keys from multiple properties or with custom formatting. This ensures consistent key formats across your application.
Simple Computed Keys
Create a key from a single property with a format string:
[DynamoDbTable("products")]
public partial class Product
{
// Source property
public string ProductId { get; set; } = string.Empty;
// Computed from ProductId with format string
[PartitionKey]
[Computed(nameof(ProductId), Format = "PRODUCT#{0}")]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
}
The source generator creates a key builder that applies the format:
// Generated ProductKeys class
public static class ProductKeys
{
public static string Pk(string productId)
{
return $"PRODUCT#{productId}";
}
}
// Usage
ProductKeys.Pk("prod123") // Returns "PRODUCT#prod123"
Multi-Property Computed Keys
Combine multiple properties into a single key:
[DynamoDbTable("events")]
public partial class Event
{
public string TenantId { get; set; } = string.Empty;
public string EventType { get; set; } = string.Empty;
// Computed from TenantId and EventType
[PartitionKey]
[Computed(nameof(TenantId), nameof(EventType), Format = "TENANT#{0}#EVENT#{1}")]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
}
// Generated EventKeys class
public static class EventKeys
{
public static string Pk(string tenantId, string eventType)
{
return $"TENANT#{tenantId}#EVENT#{eventType}";
}
}
// Usage
EventKeys.Pk("tenant123", "LOGIN") // Returns "TENANT#tenant123#EVENT#LOGIN"
DateTime Format Strings
Use .NET format specifiers for DateTime properties:
[DynamoDbTable("logs")]
public partial class LogEntry
{
public DateTime Timestamp { get; set; }
// Format as ISO 8601: 2024-03-15T10:30:00.000Z
[PartitionKey]
[Computed(nameof(Timestamp), Format = "LOG#{0:o}")]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
// Format as date only: 2024-03-15
[SortKey]
[Computed(nameof(Timestamp), Format = "{0:yyyy-MM-dd}")]
[DynamoDbAttribute("sk")]
public string DateKey { get; set; } = string.Empty;
}
Common DateTime formats:
| Format | Example Output | Use Case |
|---|---|---|
:o | 2024-03-15T10:30:00.000Z | ISO 8601, sortable |
:yyyy-MM-dd | 2024-03-15 | Date only |
:yyyy-MM | 2024-03 | Year-month grouping |
:yyyyMMddHHmmss | 20240315103000 | Compact timestamp |
Numeric Format Strings
Format numbers with padding or precision:
[DynamoDbTable("versions")]
public partial class Version
{
public int VersionNumber { get; set; }
// Format with zero-padding: v001, v002, v010, v100
[SortKey]
[Computed(nameof(VersionNumber), Format = "v{0:D3}")]
[DynamoDbAttribute("sk")]
public string VersionKey { get; set; } = string.Empty;
}
Common numeric formats:
| Format | Example Output | Use Case |
|---|---|---|
:D3 | 001, 010, 100 | Zero-padded integers |
:F2 | 10.50 | Fixed-point decimals |
:N0 | 1,000 | Thousands separator |
Default Separator (No Format)
Without a format string, properties are joined with the default # separator:
[DynamoDbTable("composite")]
public partial class CompositeEntity
{
public string TenantId { get; set; } = string.Empty;
public string UserId { get; set; } = string.Empty;
// Uses default separator "#"
[PartitionKey]
[Computed(nameof(TenantId), nameof(UserId))]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
}
// CompositeEntityKeys.Pk("tenant1", "user1") returns "tenant1#user1"
You can customize the separator:
[PartitionKey]
[Computed(nameof(TenantId), nameof(UserId), Separator = "|")]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
// CompositeEntityKeys.Pk("tenant1", "user1") returns "tenant1|user1"
Best Practices
Use Computed Keys for Consistency
Computed keys ensure consistent formatting across your application:
// ✅ Good - consistent key format enforced by source generator
[PartitionKey]
[Computed(nameof(UserId), Format = "USER#{0}")]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
// ❌ Avoid - manual key construction is error-prone
[PartitionKey]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
// Manual construction elsewhere:
user.PartitionKey = $"USER#{user.UserId}"; // Easy to make mistakes
Use Sortable DateTime Formats
For sort keys with timestamps, use formats that sort correctly as strings:
// ✅ Good - ISO 8601 sorts correctly
[SortKey]
[Computed(nameof(Timestamp), Format = "{0:yyyy-MM-ddTHH:mm:ss.fffZ}")]
[DynamoDbAttribute("sk")]
public string TimestampKey { get; set; } = string.Empty;
// ❌ Avoid - MM/dd/yyyy doesn't sort chronologically
[SortKey]
[Computed(nameof(Timestamp), Format = "{0:MM/dd/yyyy}")]
[DynamoDbAttribute("sk")]
public string TimestampKey { get; set; } = string.Empty;
Keep Key Formats Simple
Simple key formats are easier to understand and debug:
// ✅ Good - simple, readable format
[Computed(nameof(UserId), Format = "USER#{0}")]
// ❌ Avoid - overly complex format
[Computed(nameof(UserId), nameof(TenantId), nameof(Region),
Format = "TENANT#{1}#REGION#{2}#USER#{0}#ACTIVE")]
Next Steps
Now that you understand entities, learn how they generate Tables with type-safe operations.